Iniezione delle dipendenze tramite Costruttore vs Setter

2

AGGIORNAMENTO 2017/04/19

Un'altra vista utilizza wrapper per late binding .

Introduzione

Credo che gli oggetti dovrebbero essere immutabili, quindi ho solo impostato le proprietà tramite il costruttore. In tal caso, lo stato dell'oggetto non cambia mai.

Problema

Ci sono casi in cui i parametri richiesti per il costruttore non sono noti in quel momento. Quindi ho bisogno di:

  • un setter (non è la mia preferenza)
  • un'istanza 'hard coded' nel mio metodo (no Depency Injection, non flessibile) (non è la mia preferenza)
  • un binding posticipato (possibile soluzione, ma lo svantaggio (credo) è che il contenitore contenga troppe logiche)

Caso

Consentitemi di dare un esempio di codice semplificato per illustrare il mio problema (PHP).

interface Personable
{
    public function __toString();
}

final class Person implements Personable
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function __toString(): string
    {
        return 'Person ' . $this->name;
    }
}

final class Foo
{
    private $personable;

    public function __construct(Personable $personable)
    {
        $this->personable = $personable;
    }
}

Domanda

Supponiamo che per default leghi Personable a Person nel mio contenitore. In quel momento non ho il required name parameter . Voglio passare l'istanza di Person tramite il costruttore. Qual è la migliore pratica?

    
posta schellingerht 06.04.2017 - 12:20
fonte

3 risposte

3

Penso che la tua classe Foo non debba dipendere da un'istanza Personable . La tua classe Foo probabilmente ha un numero di metodi che puoi chiamare per far eseguire alcune azioni usando l'oggetto Personable , tuttavia direi che oggetti come Personable dovrebbero essere passati come argomento a quelle funzioni.

Class Foo
{
    public function printName(Personable $personable) 
    {
        echo (string)$personable;
    } 
} 

Provare a iniettare dipendenze stateless (servizi, repository, fabbriche, ecc.) tramite il costruttore e iniettare questi oggetti stateful (modelli di database e simili) tramite la funzione che si sta chiamando.

    
risposta data 06.04.2017 - 19:01
fonte
1

Primo:

There are cases the required parameters for the constructor aren't known at that moment.

Non ci credo davvero. E anche se così fosse, potrebbe esserci un difetto più fondamentale per l'architettura delle tue applicazioni.

Ma partendo dal presupposto che non si può fare nulla per quel problema fondamentale:

Dì che vuoi iniettare una dipendenza come questa:

interface MyDependecy {
    function doStuff(): void;
}

class MyDependencyImpl1 implements MyDependency {
    function doStuff(): void { /* stuff */ }
}

class MyDependecyImpl2 implements MyDependency {
    function doStuff(): void { /* stuff */ }
}

Non puoi decidere tra MyDependencyImpl1 e MyDependencyImpl2 nel momento in cui viene eseguito __construct dell'oggetto dipendente.

Ecco come risolverlo:

class MyDependencyDelegator implements MyDependency {
    /** @var ?MyDependency */ private $target;

    function __construct(?MyDependency $target) { $this->setTarget($target); }

    function doStuff(): void { $target->doStuff(); }

    function setTarget(MyDependecy $target) { $this->target = $target; }
}

Nella configurazione DI:

$delegator = new MyDependencyDelegator();
$this->when(MyDependentObject::class)->needs(MyDependecy::class)->give($delegator); // i dont remember the exact syntax right now, forgive me

// call $delegator->setTarget when the desicion can be made

Ora, questo si riduce all'iniezione di setter. Non risolve il fatto che l'informazione non è presente quando dovrebbe essere (al momento della costruzione dell'oggetto dipendente). Questo approccio trattiene la parte "sporca" dal tuo oggetto principale e la avvicina al problema: la configurazione DI.

    
risposta data 06.04.2017 - 13:35
fonte
1

Poiché l'argomento per la creazione di un'istanza di Personable è sconosciuto durante la creazione di Foo - è necessario crearlo in un secondo momento.

Puoi introdurre PersonableFactory astrazione, che sarà responsabile della creazione di Personable .

interface PersonableFactory
{
    public function create();
}

final class PersonFactory implements PersonableFactory
{
    public function create(string $name): string
    {
        return new Person($name);
    }
}

final class Foo
{
    private $personableFactory;

    public function __construct(PersonableFactory$personableFactory)
    {
        $this->personableFactory = $personableFactory;
    }
}

Nel caso in cui Person sia solo un oggetto dati, penso che non ti serva un'iniezione di fabbrica o di dipendenza: crea semplicemente una nuova istanza quando il tuo nome sarà conosciuto

    
risposta data 06.04.2017 - 16:05
fonte

Leggi altre domande sui tag