Come interpretare il principio di inversione di dipendenza

1

Sto lavorando per cercare di capire i principi di progettazione orientata agli oggetti SOLID. Sono stato in grado di ottenere il "SOL" abbastanza facilmente, anche se "L" ha richiesto un po 'di tenuta perché sono cattivo nelle definizioni. Io penso capisco l '"io" (potrei non farlo, il che potrebbe smentire la mia confusione). Ma "D" o Dependency Inversion mi sta facendo incazzare. A seconda delle astrazioni, piuttosto che delle concrezioni ha senso in un certo senso, ma l'applicazione effettiva mi sta facendo inciampare, specialmente quando il termine "nuovo è colla" è gettato lì dentro. Puoi dirmi dove sto sbagliando in un esempio di codice mentre sto cercando di fare il salto da un concetto all'altro.

Supponiamo di avere una classe astratta in C #:

  public abstract class IMakeAnIntSetArray
  {
    public void Populate(int FirstItem, int NumberOfItems)
    {
      mySet.Add(FirstItem);
      NumberOfItems--;
      for (int i = 0; i < NumberOfItems; i++)
      {
        mySet.Add(GetNext());
      }
    }

    protected abstract int GetNext();

    protected List<int> mySet;

    public List<int> Set
    {
      get { return mySet; }
    }
  }

È implementato dalle classi

  public class CountNumbersLikeAHuman : IMakeAnIntSetArray
  {
    protected override int GetNext()
    {
      int next = mySet.Last();
      next++;
      return next;
    }
  }

e

  public class SomeMathInvolved : IMakeAnIntSetArray
  {
    protected override int GetNext()
    {
      int next = mySet.Last();
      if(next % 2 == 0)
      {
        return next / 2;
      }
      else
      {
        return ((next * 3) + 1);
      }
    }
  }

Uno dei seguenti è un modo corretto per applicare il principio di inversione delle dipendenze o mi manca qualcosa? Come modifico questo per utilizzare correttamente il DIP? O sono semplicemente attaccato a "new is glue"?

  class Program
  {
    static void Main()
    {
      CountNumbersLikeAHuman simpleEnough = new CountNumbersLikeAHuman(); //wrong?
      SomeMathInvolved akaChangeYourMajorToEnglish = new SomeMathInvolved(); //also wrong?

      //Or . . . 

      IMakeAnIntSetArray useYouFingersAndToes = new CountNumbersLikeAHuman();
      IMakeAnIntSetArray makeItWayMoreComplicated = new SomeMathInvolved();

      //Or am I completely off here? 
    }
  }
    
posta Jake 26.09.2018 - 03:54
fonte

2 risposte

2

Suppongo che tu stia cercando un esempio che utilizzi la dipendenza injection (che è il caso tipico dell'utilizzo del principio di inversione delle dipendenze nel contesto di SOLID). Per questo, prima hai bisogno di due classi che possono dipendere l'una dall'altra e dove la dipendenza tra di esse può essere risolta "iniettando" oggetti del primo nel secondo. La sezione di codice in Program quindi dovrebbe assemblare quelle parti insieme.

Quindi l'esempio nella tua domanda manca un'altra classe in cui la dipendenza viene iniettata. Ai fini della dimostrazione, ignoriamo per un momento il fatto che la tua classe base astratta sia erroneamente denominata (è un'astrazione per un insieme di numeri interi, quindi dovrebbe avere un nome che lo riflette, il verbo Make nel nome fa senza senso). Cerchiamo anche di ignorare il fatto che IMakeAnIntSetArray mischia l'interfaccia e l'ereditarietà dell'implementazione, questo non ha importanza per quanto segue, possiamo pensare a IMakeAnIntSetArray come pura interfaccia, senza alcun codice.

Iniziamo ad aggiungere un'altra classe, chiamiamola ApplicationForm . Ecco un'implementazione che ignora il DI:

 class ApplicationForm
 {
      IMakeAnIntSetArray _mySet;

      public ApplicationForm(bool countLikeAHuman)
      {
          if(countLikeAHuman)
              _mySet=new CountNumbersLikeAHuman();
          else
              _mySet=new SomeMathInvolved();

      }
      // ... some functions using _mySet
 }

Qui, la classe ApplicationForm dipende direttamente da CountNumbersLikeAHuman e SomeMathInvolved . È vincolato a queste due derivazioni e deve essere collocato in una libreria o in un livello in cui è possibile fare riferimento direttamente a queste due classi.

Contro questo, guarda questo disegno:

  class ApplicationForm
  {
      IMakeAnIntSetArray _mySet;

      public ApplicationForm(IMakeAnIntSetArray mySet)
      {
          _mySet = set;
      }
      // ... some functions using _mySet
 }

Ora, ApplicationForm non dipende più direttamente dalle altre due classi, dipende solo dall'astrazione IMakeAnIntSetArray . Il chiamante, tuttavia, deve decidere quale derivazione di IMakeAnIntSetArray verrà iniettata:

class Program
{
  static void Main()
  {
       ApplicationForm appForm;

       // make a decision, for example, by evaluating command like arguments
       appForm = new ApplicationForm(new CountNumbersLikeAHuman());
       //or
       appForm = new ApplicationForm(new SomeMathInvolved());

       // ... use appForm somehow ...
  }

}

Come puoi vedere, CountNumbersLikeAHuman o SomeMathInvolved è iniettato in ApplicationForm . Questo ha diversi vantaggi. Ad esempio, è possibile fornire un'implementazione fittizia di IMakeAnIntSetArray e iniettarla in ApplicationForm a scopo di test. Oppure, potresti creare nuove derivazioni di IMakeAnIntSetArray e iniettarle in ApplicationForm senza dover modificare il codice di ApplicationForm . Ciò rende possibile mettere ApplicationForm in una libreria nera riutilizzabile, in cui la lib contiene solo IMakeAnIntSetArray , ma le derivazioni dipendono dal chiamante / utente di quella libreria.

    
risposta data 26.09.2018 - 06:20
fonte
0

In pratica significa che il tuo codice dipende dall'astrazione piuttosto che dai dettagli e il cliente possiede l'astrazione (la parte di inversione).

Ad esempio, hai una classe che deve registrare alcuni messaggi. (il codice di nota è puramente indicativo e non consiglia la registrazione come questa)

class DoStuffService{

public void doStuff(){
    FileLogger logger= new FileLogger("C:\mylog.txt");
    Logger.log(DateTime.Now);
}
}

Nel codice precedente, DoStuffService dipende da un file Logger concreto. La dipendenza non è invertita e il codice non è flessibile. Se si desidera accedere al registro eventi di Windows anziché al file system, è necessario modificare il servizio DoStuff per creare un'istanza del programma di registrazione diversa.

Ecco il codice refactored in modo da utilizzare l'inversione di dipendenza tramite l'integrazione delle dipendenze.

    class DoStuffService{
        ILogger m_logger;

        DoStuffService(ILogger logger){
            m_logger = logger;
        }

        public void doStuff(){
            logger.log(DateTime.Now);
        }

In questo codice si osservi che DoStuffService dipende da un'interfaccia di ILogger (un'astrazione) e non da un'implementazione concreta di un logger.

Se si desidera modificare questo codice per scrivere nel registro eventi, è sufficiente passare un'istanza di EventLogger su di esso tramite il costruttore. Non sarebbe necessario modificare la classe DoStuffService. Quindi è più flessibile e resiliente da cambiare.

Concettualmente il codice si preoccupa e dipende da un'astrazione "Log" che non si preoccupa dei dettagli e non dovrebbe essere accoppiato a un'implementazione specifica.

Se il progetto che ospitava il DoStuffService ospitava anche l'interfaccia di ILogger e le implementazioni concrete dei logger erano ospitate in un progetto diverso, si noterebbe che le dipendenze del codice sorgente sono invertite - Il progetto Logger dipende dal progetto DoStuff quindi "dipendenza inversione".

    
risposta data 02.11.2018 - 16:11
fonte

Leggi altre domande sui tag