Perché l'iniezione di dipendenza incoraggia la collaborazione a essere esposta tramite costruttori?

1

L'approccio generale a DI che vedo in risposte come So Singletons sono cattivi, allora cosa? incoraggia oggetti di business che collaborano con altri oggetti a (a) no creare direttamente tali istanze e (b) farle approvare a costruzione. Posso capire (a), ma non (b). Questo sembra accadere il più delle volte in risposta a un uso eccessivo di Singletons. Ma perché non basta un approccio modificato a singleton:

class SingleInstance {
  virtual foo();
  virtual bar();

  static SingleInstance getInstance() {
    if(instance_ == null) {
      instance_ = new SingleInstance();
    }
    return instance_;
  }

  void setMockInstance(SingleInstance s) {
    assert(instance_ == null);
    instance_ = s;
  }

  static SingleInstance instance_;
}

Quindi ora questo non è più Singleton con la maiuscola S (per Misko Hevery) ma in questo caso applica ancora un'istanza. Tutto il codice vuole accedere alla singola istanza può ancora chiamare S :: getInstance () senza ingombrare i loro costruttori richiedendo esplicitamente il istanza da passare. L'impostazione predefinita può essere ancora pigramente inizializzato nel codice di produzione, ma per il test può ancora essere deriso.

Nella risposta referenziata il primo vantaggio di DI è elencato come:

It makes the code easier to read; you can clearly understand from
the interfaces exactly what data the dependent classes depend on.

Ma perché non è una violazione dell'incapsulamento? Ho davvero bisogno di sapere dall'interfaccia pubblica tutto ciò che accede al SingleInstance / Database / etc?

Supponiamo di avere un database e 30 TableGateway responsabili delle classi per le operazioni CRUD su quelle tabelle. Nell'approccio DI TableGateway i costruttori accetterebbero il Database nel suo costruttore. Poi un la classe di business logic accetta le tabelle con cui collabora / utilizza:

class BusinessLogic {
  BusinessLogic(Table1 t1, Table2 t2, Table3 t3);
  void doBusiness() {
    t1_.query(...);
    t2_.insert(...);
    t3_.update(...);
  }
}

In che modo questo sfasamento di dipendenze esplicite nel costruttore consiglia?

    
posta Daniel Davidson 07.10.2014 - 22:47
fonte

3 risposte

2

Il tuo approccio modificato a singleton è ancora un singleton e presenta la maggior parte degli inconvenienti di un singleton:

  • Viene comunque introdotto un elevato accoppiamento attraverso l'applicazione - cioè, se un giorno si desidera effettuare il refactoring lontano da Singleton, si avrà difficoltà a farlo;
  • Fornisce ancora un modo per qualsiasi componente di collaborare con il singleton - il che significa che potenzialmente, il singleton può comunicare con qualsiasi parte del sistema - ma se si va per qualsiasi tipo di modello architettonico stratificato (MVC, MVVM, 3 livelli), quindi il singleton è un punto che deve essere controllato.

L'incapsulamento è legato al fatto che un oggetto espone le operazioni supportate senza richiedere che i client siano a conoscenza dell'implementazione, ma quando invochi un costruttore, l'oggetto non esiste ancora . Il costruttore è un dettaglio di implementazione dell'oggetto , equivalente a un metodo static . In altre parole, una volta creato l'oggetto, gli argomenti del costruttore sono irrilevanti per il funzionamento dell'oggetto.

Per quanto riguarda il "churn" delle dipendenze esplicite, rendere esplicite le dipendenze è uno degli obiettivi dell'iniezione del costruttore. Se ne hai troppi, è preso come un segno che un refactoring, o una riorganizzazione, è in ordine. (Ad esempio, le tabelle relative al gruppo in un gruppo logico).

    
risposta data 08.10.2014 - 09:41
fonte
3

I dati statici (incluse le istanze singleton) sono problematici per una serie di motivi. Non è thread-friendly. È stato aggiunto un metodo per impostare la singola istanza su un'istanza di simulazione. Presumibilmente ciò viene fatto per supportare i test unitari. Questo fallirà se i test vengono eseguiti in parallelo e richiedono mock diversi per il tuo Singleton . Peggio ancora, i test falliranno quando correranno insieme ma passeranno quando vengono eseguiti separatamente.

Anche senza eseguire test in parallelo, i dati statici introducono uno stato globale che viene mantenuto tra i test. Un test che dipende dai dati statici può passare in modo affidabile quando viene eseguito da solo rispetto allo stato globale iniziale, ma fallisce costantemente se eseguito dopo un altro test che modifica lo stato globale.

Per questi motivi, i dati statici mutabili dovrebbero essere evitati. Ho visto molti più usi errati di dati statici mutevoli rispetto a usi corretti.

    
risposta data 08.10.2014 - 09:35
fonte
2

Il motivo per cui fornisco dipendenze ai costruttori è:

  1. La maggior parte delle volte le dipendenze sono richieste, quindi perché non assegnarle al costruttore
  2. Restituirli all'oggetto in un altro modo rende il loro utilizzo un po 'più complesso, dal momento che devi assicurarti di avere la dipendenza

Ovviamente questo non conta davvero quando si utilizza un framework che controlla l'iniezione delle dipendenze, perché il framework si assicurerà che tutte le dipendenze siano impostate prima di restituire l'oggetto richiesto.

    
risposta data 08.10.2014 - 08:40
fonte

Leggi altre domande sui tag