Prefazione
Per ogni progetto dato, la risposta a questa domanda sarà probabilmente diversa. Questo è semplicemente un risultato della struttura e della filosofia generale. Può essere facile e diretto in alcuni casi, ma estremamente difficile e complicato in altri.
Tuttavia , se questo è un problema difficile, si tratta di un odore di codice molto strong: è probabile che qualcosa non funzioni nel tuo progetto. Detto questo, siamo stati tutti lì, e spesso non abbiamo il tempo di riscrivere l'intera base di codice. Ancora, per iniziare: qual è il modo giusto per risolvere questo?
Il modo giusto
IoC (Inversion of Control) è un modello di progettazione e un principio che ha costantemente ha guadagnato la trazione. Fondamentalmente afferma che invece di oggetti che costruiscono ciò di cui hanno bisogno, richiedono un'istanza di ciò di cui hanno bisogno da qualche contenitore IoC, che può dare loro la "cosa" appropriata.
In questo caso, potremmo fare qualcosa di simile al seguente:
interface SystemAdapterInterface
{
public function sleep($duration);
}
class WakefulSystemAdapter implements SystemAdapterInterface
{
public function sleep($duration)
{
sleep($duration);
pcntl_signal_dispatch();
}
}
class SleepySystemAdapter implements SystemAdapterInterface
{
public function sleep($duration)
{
sleep($duration)
}
}
class SleepingEntity
{
public function __construct(SystemAdapterInterface $system)
{
$this->system = $system
}
public function mayReceiveSignal()
{
$this->system->sleep(100000);
}
}
// Register the system and sleeping entity with the IoC Container
// Using the LoEP Container for IoC
$container = new League\Container\Container;
$container->add('SystemAdapterInterface', 'WakefulSystemAdapter ');
$container
->add('SleepingEntity')
->withArgument('SystemAdapterInterface');
//Now we can easily get an instance of sleeping entity.
$container->get('SleepingEntity');
In questo esempio, utilizziamo un contenitore IoC
per gestire ciò che System
SleepingEntity
utilizza. La mia raccomandazione per un IoC
è Container
, dalla Lega dei pacchetti straordinari .
Questa soluzione mantiene il nostro SleepingEntity
disgiunto dal nostro System
. A seconda di dove usiamo SleepingEntity
, possiamo semplicemente configurare il nostro IoC Container
in modo diverso. È bello perché è relativamente semplice, facile da testare e consente l'espansione in futuro.
Sfortunatamente, questo non funziona particolarmente bene se hai già un sacco di codice con sleep (o chiamate di funzioni globali simili) e devi modificare il loro comportamento. Quindi cosa possiamo fare a riguardo?
The Not Quite as Right Way
Ok, quindi vuoi fare The Right Thing , ma il codice è scarsamente documentato, ha dipendenze che si intrecciano in ogni direzione, ed è un ascensore incredibilmente pesante da superare e assicurarti hai passato il tuo IoC
in ogni direzione lungo l'albero fino a quando non viene utilizzato per istanziare l'oggetto giusto, ogni volta.
La cosa migliore da fare è essere lungimiranti. Se vuoi rendere il codebase migliore, più disaccoppiato, fai il primo passo con questa aggiunta.
Supponiamo di avere il seguente:
class DoSomethingAndSleep
{
public function foo($a, $b, $c)
{
$a->thing();
$b->thing();
sleep(5);
$c->thing();
}
}
Ora facciamo finta che DoSomethingAndSleep
sia usato 1000 diversi posti da tutti i tipi di codice diverso. Infatti, a volte viene istanziato in modo anonimo attraverso una variabile di classe, quindi trovare quei 1.000 posti è difficile. Davvero, davvero difficile.
Bene. Spostiamo la qualità del disaccoppiamento in DoSomethingAndSleep
. Spero che tu abbia il tempo di ridefinire le cose una per una in futuro.
class DoSomethingAndSleep
{
public function __construct(SystemAdapterInterface $system=null)
{
if ($system=== null)
{
$system = App::getIoC()->get('SystemAdapterInterface');
}
$this->system = $system;
}
public function foo($a, $b, $c)
{
$a->thing();
$b->thing();
$this->system->sleep(5);
$c->thing();
}
}
Ok, quindi cosa sta succedendo qui? In primo luogo, riconosciamo che non disponiamo dell'infrastruttura giusta per passare il IoC
in DoSomethingAndSleep
, o per usarlo per costruire il DoSomethingAndSleep
, quindi, invece, ignoreremo tutto questo e ottieni il% "standard"% co_de dal nostro IoC
. Ciò significa che all'avvio dell'applicazione, dobbiamo:
- istanzia il contenitore IoC
- registra SystemAdapterInterface con il contenitore IoC
- registra il contenitore IoC con l'app
Questo è accettabile. Dato che abbiamo un argomento predefinito nel nostro costruttore di App
, possiamo dedicare del tempo a spostare in avanti le attività di refactoring per utilizzare DoSomethingAndSleep
senza rompere la compatibilità all'indietro.
Dovremo comunque passare a (si spera) usando IoC Container
per gestire oggetti e istanze, ma almeno siamo al punto in cui stiamo registrando il Container
e usando quello registrato invece di istanziare direttamente esso.
Se stai utilizzando SystemAdapterInterface
, possiamo anche registrare League Container
con il nostro DoSomethingAndSleep
utilizzando questo metodo e sfruttare il Auto Wiring e il delegato di Reflection per occuparsi di questa nuova dipendenza per IoC Container
.
$container = new League\Container\Container;
// register the reflection container as a delegate to enable auto wiring
$container->delegate(
new League\Container\ReflectionContainer
);
$doSomethingAndSleep = $container->get('DoSomethingAndSleep');
Questo dovrebbe rendere il nostro futuro refactoring andare anche più agevolmente.
Conclusione
È sempre difficile capire come prendere un vecchio codice base monolitico e renderlo lentamente migliore, più gestibile. Non è possibile interrompere la compatibilità con le versioni precedenti, è necessario andare avanti con nuove funzionalità e assicurarsi che le aggiunte migliorino le cose. È difficile. Penso che il miglior suggerimento sia questo: se non puoi scrivere test per questo, avrai un tempo terribile per mantenerlo. Pertanto, ogni volta che apporti una modifica, assicurati di poter scrivere un test unitario contro tale modifica.
In questo caso, l'utilizzo di DoSomethingAndSleep
lo fa con pochissimo sforzo. Anche se il tuo progetto non è strutturato per usarlo, puoi introdurlo inizialmente e usarlo solo in un posto, e poi col passare del tempo puoi rifattorizzare il tuo codice finché alla fine tutto sta usando DI ed è piacevolmente disaccoppiato.