L'esposizione alla classe di implementazione è corretta in Dependency Inversion

2

Fondamentalmente dovremmo dipendere dalle astrazioni invece che dalle classi concrete, questo è il principio principale di Dependency Inversion.

interface ITest
{
    void DoSomething();
}

class TestImpl : ITest
{
    public void DoSomething()
    {}
}

class UseTest
{
    private ITest _test = null;

    public UseTest(ITest test)
    {
        _test = test;
    }

    public void UseCall()
    {
        _test.DoSomething();
    }
}

Avremo sempre una classe che implementerà la nostra interfaccia.

Quindi in quel caso il mio team potrebbe ancora fare un cattivo uso della classe di implementazione:

TestImpl testImpl = new TestImpl();
testImpl.DoSomething();

Invece dell'approccio desiderato che sarebbe:

UseTest useTest = new UseTest(new TestImpl());
useTest.UseCall();

Se non implementi un dettato di inversione delle dipendenze (tutti dovrebbero usare le astrazioni) hai enormi probabilità che qualcosa usi male le classi di implementazione dell'interfaccia.

C'è un modo per evitare l'uso improprio della classe di implementazione dell'interfaccia?

    
posta John Peter 13.12.2018 - 14:41
fonte

4 risposte

6

Questa è un'area in cui il sistema dei tipi di una lingua non ti dà molte garanzie. Senza rendere la classe un caso a livello di pacchetto nel caso di Java, o una classe interna nel caso di C #, non è possibile controllare chi crea istanze di questa classe. Una classe pubblica con un costruttore pubblico può essere istanziata da chiunque. Anche se lo rendi interno / pacchetto-privato, qualsiasi codice dentro quel pacchetto o assembly può istanziarlo.

Questo è un problema da risolvere durante la revisione del codice. Il compilatore non sarà di grande aiuto in questo caso, se non si sta utilizzando un framework per le dipendenze che ha questo tipo di limiti di esecuzione integrati.

    
risposta data 13.12.2018 - 14:58
fonte
2

Dipende dal framework / libreria DI che si sta utilizzando. Potrebbe esserci qualcosa che aiuta a prevenire l'uso improprio delle implementazioni concrete.

Senza una libreria DI, puoi considerare di separare le classi che sono usate in un assembly diverso, a parte le classi che le usano e quindi dichiarano le classi di implementazione interne.

    
risposta data 13.12.2018 - 14:54
fonte
2

Il principio di inversione delle dipendenze riguarda più le dipendenze tra moduli / progetti / pacchetti / librerie che tra classi.

Modulo / Dominio del progetto

public interface IRepository
{
    void Save(object data);
}

public class Order
{
    private readonly IRepository _repository;

    public Order(IRepository repository) => _repository = repository;

    public void Save(object data) => _repository.Save(data)

}

Modulo / Database del progetto

using Domain;

public class SqlServerRepository : IRepository
{
    public void Save(object data)
    {
        // SqlConnection, SqlCommand, Execute ...
    }
}

L'applicazione avrà solo un modulo "principale" - Punto di ingresso, che conoscerà tutti gli altri moduli.
Il modulo del punto di ingresso sarà responsabile della compilazione del grafico dell'oggetto richiesto per la tua applicazione (istanziare e iniettare le implementazioni corrette)

--------------------------                    ------------------------
|      **Domain**        |                    |     **Database**     |   
|                        |   <--------------  |                      |  
|      IRepository       |                    |                      |  
--------------------------                    ------------------------ 
            ^                                              ^
            |                                              |
            |                                              |
            |            -----------------------           |
            |            |       **Main**      |           |
            |------------|                     |-----------|
                         |                     |       
                         -----------------------

Punto di ingresso

using Domain;
using Database;

public class Program
{
    public static void Main()
    {
        var sqlServerRepository = new SqlServerRepository();
        var order = new Order(sqlServerRepository);

        Run(order); // Run appliciation
    }
}

Come puoi vedere, il modulo Dominio non è in grado di creare un'istanza di SqlServerRepository .

Il principio di inversione delle dipendenze applicato per i moduli impedirà al modulo Dominio di istanziare le istanze del modulo Database solo perché il modulo Dominio non conoscerà l'esistenza del modulo Database.

L'idea del principio Dependency Inversion è di cambiare la direzione di dipendenza che abbiamo in fase di esecuzione durante la fase di progettazione.

Direzione dipendenza runtime

Order -> SqlServerRepository

Direzione dipendenza dal tempo di progettazione

SqlServerRepository -> Order

Uno dei vantaggi è la riduzione dei tempi di costruzione dei grandi progetti quando si apportano modifiche in moduli di basso livello.
Ad esempio: posso apportare modifiche al modulo Database senza dover ricostruire il modulo Dominio.

L'inversione della dipendenza all'interno di un modulo non ha molto senso, dal momento che le classi già sanno l'una dell'altra e il cambiamento in una classe richiederà la ricostruzione di un modulo.

Non è necessario scrivere contro l'interfaccia all'interno di un modulo, a meno che non si disponga di implementazioni diverse per un'astrazione.

    
risposta data 13.12.2018 - 23:58
fonte
1

Se non vuoi che TestImpl abbia un metodo pubblicabile pubblicamente; perché gli dai un metodo pubblicamente pubblicabile? Se vuoi solo UseTest per poter eseguire questa azione, solo UseTest dovrebbe avere un metodo pubblicabile pubblicamente.

Ci sono diversi modi per evitarlo. I due principali a cui riesco a pensare sono:

  1. Crea TestImpl una classe di dati pura e implementa solo la logica del metodo in UseTest , costringendo così i chiamanti a utilizzare UseTest se vogliono chiamare il metodo.
  2. Crea TestImpl una classe nidificata in UseTest e imposta il suo metodo su privato. UseTest sarà in grado di accedere a TestImpl.DoSomething() ma gli altri chiamanti no.
risposta data 14.12.2018 - 08:08
fonte