Sono necessarie le fabbriche quando si fa l'iniezione di dipendenza?

4

Sto cercando di refactoring del codice, quindi usa l'integrazione delle dipendenze.

Prendi questa classe di esempio (senza senso):

class Foo {
  protected $min;
  protected $max;
  public $bar;

  public function __construct($min, $max) {
    $this->min = $min;
    $this->max = $max;

    $this->bar = new Bar($this->calc());
  }

  public function calc() {
    $number = rand($this->min, $this->max);
    return new Quz($number);
  }
}

Per utilizzare l'iniezione di dipendenza (con contenitori), la cambierei in:

class Foo {
  protected $barFactory;
  protected $quzFactory;

  protected $min;
  protected $max;
  public $bar;

  public function __construct(BarFactoryInterface $barFactory, QuzFactoryInterface $quzFactory) {
    $this->barFactory = $barFactory;
    $this->quxFactory = $quxFactory;
  }

  public function init($min, $max) {
    $this->min = $min;
    $this->max = $max;

    $this->bar = $this->barFactory->create($this->calc());
  }

  public function calc() {
    $number = rand($this->min, $this->max);
    return $this->quxFactory->create($number);
  }
}
  • Ho davvero bisogno di un metodo o una classe factory per ogni classe?
  • Ho davvero bisogno della funzione init() per sostituire il costruttore?
  • O lo sto facendo terribilmente male?
posta Jasny - Arnold Daniels 01.10.2016 - 05:16
fonte

4 risposte

5

Do I really need a factory method or class for each class?

Dato che stai creando istanze di "Bar" e "Quz", ha senso utilizzare le fabbriche per disaccoppiarle.

Tuttavia, stai creando solo Bar nel costruttore, quindi non puoi prendere una factory per questo, basta prendere un'istanza di Bar . Fare così sarebbe un esempio di iniezione di dipendenza senza una fabbrica; permette al codice client di fornire l'oggetto ... usando una fabbrica se questa è l'intenzione, o meno. Infatti, non stai utilizzando Bar , puoi saltarlo.

Modifica: noto che desideri utilizzare il risultato di calc per creare Bar . Prendendo la factory al posto dell'istanza Bar otterresti un incapsulamento migliore in quanto garantisce che Bar sia stato creato con un risultato valido per Quz dato il min e max fornito ... ancora, bar è pubblico e il codice client potrebbe sovrascriverlo che non soddisfa tale protezione.

Come per Quz , dal momento che creerai una nuova istanza ogni volta che il codice client chiama calc , avrai bisogno della fabbrica per ottenere più istanze.

Do I indeed need the init() function to replace the constructor?

Puoi prendere le fabbriche nel costruttore. L'utilizzo di un metodo separato crea un problema perché consente allo sviluppatore di chiamare il costruttore e saltare la chiamata init .

Ora, se volessi nascondere le fabbriche e fare in modo che il codice client crei più istanze che condividono le stesse fabbriche ... beh, potresti creare una fabbrica per questo. Devo dire che non c'è bisogno di farlo.

Or am I doing it terribly wrong?

Separazione di init dal costruttore. Considera cosa succede se il codice client crea un'istanza e chiama calc senza chiamare init : nel tuo codice il numero casuale sarà 0 (o qualsiasi valore predefinito) o cestino perché il tuo campo non è stato inizializzato (a seconda del tuo lingua).

Potrebbe essere stato peggio, se effettivamente hai utilizzato Bar , non sarebbe stato inizializzato perché lo fai in init , quindi sarebbe un puntatore non valido.

    
risposta data 01.10.2016 - 05:30
fonte
3

L'iniezione di oggetti o fabbriche risolve scopi diversi e comunica intenzioni diverse.

Se passi una fabbrica, il tuo nuovo oggetto può o non può creare nuovi oggetti da quella fabbrica. Questi oggetti non possono essere preconfigurati (beh, almeno non senza odori di codice come l'impostazione di proprietà statiche in fabbrica). Puoi solo iniettare cose che hanno fabbriche. A volte, questo potrebbe essere esattamente ciò che vuoi - ma poi, puoi vederlo come se stessi iniettando un oggetto ordinario che sembra essere una fabbrica.

Se si inietta un oggetto "ordinario", è possibile passare qualsiasi cosa - gli oggetti senza fabbriche non sono un problema. Creare classi di fabbrica solo a tale scopo sembra sconsiderato.

Se ti chiedi perché vuoi inserire il codice, probabilmente vorrai passare configurazione / stato / comportamento / qualunque-tu-chiama-it. Le fabbriche passanti lo limitano molto. Ad esempio, come è possibile passare un connettore del database che non è possibile configurare? E non può essere riutilizzato neanche.

Penso che tu stia confondendo i concetti qui (vale a dire pattern di fabbrica e iniezione di dipendenza) - che possono essere usati insieme, ma non è necessario.

    
risposta data 01.10.2016 - 09:30
fonte
2

In genere è logico utilizzare le fabbriche quando sono soddisfatte le due condizioni:

  1. Non sai al momento della compilazione quali sono le tue dipendenze
  2. Il processo di impostazione di tali dipendenze è "complicato"

Quando questi due criteri non vengono soddisfatti, il solo passaggio delle istanze attraverso i costruttori funziona abbastanza bene e ti dà il miglior risultato possibile.

Il principio di base della fabbrica stessa è il criterio 2, quindi a meno che non ci sia, non ha nemmeno senso usare una fabbrica, DI o no.

    
risposta data 01.10.2016 - 06:23
fonte
1

L'originale Foo fa tre cose:

  1. $this->bar = new Bar(new Quz(rand($min, $max))
  2. definisce calc() come new Quz(rand($min, $max))
  3. ricorda $min e $max in modo che calc non li abbia superati

L'originale Foo sta già facendo l'iniezione di dipendenza. È principalmente costruzione. La cosa di rand è un tocco di comportamento e mi piace molto separare il comportamento dalla costruzione, quindi avere un codice hard in costruzione è un po 'strano per me, ma non è il mio punto debole.

La mia lamentela principale è che non vedo nulla di simile all'uso. Questo si legge come un esempio forzato. Non riesco davvero a vedere che tutto ciò stia aiutando.

Ma va bene. Immaginerò come viene usato il% originale di% co_de. Supponiamo che abbia bisogno di un Foo fatto come Bar rende Foo . Oh, e ho bisogno di un po 'di Bar . Così faccio questo:

$foo = new Foo(1,10);
$bar = $foo.bar;
$quz1 = $foo.calc();
$quz2 = $foo.calc();
$quz3 = $foo.calc();

Tutto ciò è codice di costruzione. Tutto quello che mi ha comprato non era dover scrivere in questo modo:

How can I implement the above class with DI but without factories?

$min = 1;
$max = 10;
$bar = new Bar(new Quz(rand($min, $max));
$quz1 = new Quz(rand($min, $max));
$quz2 = new Quz(rand($min, $max));
$quz3 = new Quz(rand($min, $max));

Che, lo giuro su Dio, è in realtà più facile da leggere. È anche incredibilmente flessibile. Inietterò ciò che mi piace. Userò qualsiasi metodo che mi piace su $ min e $ max.

Ma bene, vediamo cosa otteniamo quando passiamo al Quz migliorato:

$foo = new Foo(new BarFactoryDefault(), new QuzFactoryDefault());
$foo.init(1,10);
$bar = $foo.bar;
$quz1 = $foo.calc();
$quz2 = $foo.calc();
$quz3 = $foo.calc();

Or am I doing it terribly wrong?

Si

Oltre a quanto sopra è più complicato di una delle due soluzioni precedenti, devo ancora creare queste classi predefinite, per non parlare delle interfacce per esse. Sì, questo è terribilmente sbagliato.

Ciò che stai facendo è raggiungere gli schemi e le tecniche che hai ascoltato erano buone e pensavo che usando buoni ingredienti avresti cucinato del buon cibo. Assapora sempre il cibo che cucini prima di servirlo. Mi piace il ketchup e mi piace il latte. Non mi piace il ketchup nel mio latte. Non è un problema né con il ketchup né con il latte.

Il problema qui è che l'esempio è anemico. I problemi che queste tecniche risolvono non sono in questo esempio. Quindi provare a esplorare le tecniche qui diventa un festival di ingegneria eccessiva che non ti insegna nulla di buono.

Hai fatto qualche altra domanda su cui mi occuperò successivamente, ma il punto principale che voglio fare qui è non progettare queste classi senza pensare a come vengono utilizzate.

Are factories required when doing dependency injection?

No.

L'iniezione di dipendenza non richiede fabbriche. Le fabbriche sono uno dei tanti modelli di costruzione. L'iniezione di dipendenza non richiede nemmeno schemi di costruzione. Puoi farlo in main. Le fabbriche potrebbero eseguire iniezioni. Le fabbriche possono essere iniettate. Fabbriche astratte dettagli di costruzione. Utilizzi principalmente le Fabbriche per nascondere le dipendenze che vuoi trattare come valori predefiniti ragionevoli. Almeno lo faccio.

Do I really need a factory method or class for each class?

Hai bisogno di un metodo di fabbrica per ogni cerimonia di costruzione che sei stufo di guardare. È assurdo pensare che ci sia qualche rapporto 1 a 1 qui. È zero per molti.

Do I indeed need the init() function to replace the constructor?

Penso che sia un peccato imperdonabile lasciare che Foo esista in uno stato non inizializzato. Ci sono molti modi per risolverlo. Piegalo nel costruttore. Spezzalo in due oggetti. Modelli di costruttori. Trova solo un modo per non costringermi ad aggrapparmi a qualcosa che non posso usare se non seguo la sua cerimonia.

Mi dispiace che questo si sia trasformato in un tale rant. Ricordo di aver attraversato schemi di progettazione e di chiedermi di cosa si trattasse. I cattivi esempi come questo erano una grande ragione per cui ho impiegato così tanto tempo per ottenerlo. Per favore, non accettare che questa roba sia buona fino a quando non riesci a capire perché è buona. La fede è un assassino qui. Assapora il cibo prima di servirlo.

    
risposta data 01.10.2016 - 08:15
fonte