6

Se si dispone di una classe base con servizi iniettati tramite dipendenze del costruttore: È possibile dichiarare il costruttore della sottoclasse senza utilizzare : base (params)?Iniezione delle dipendenze sia nella classe base che nella sottoclasse con IoC?

public MyBaseClass 
{ 
    private IServiceA _serviceA; 
    private IServiceB _serviceB; 
    private IServiceC _serviceC; 

    public MyBaseClass(null, null, null) 
    public MyBaseClass(IServiceA serviceA, IServiceB serviceB, IServiceC serviceC) 
    { 
     _serviceA = serviceA; 
     _serviceB = serviceB; 
     _serviceC = serviceC; 
    } 
} 

E una sottoclasse con alcune dipendenze extra iniettato:

public MySubClassA : MyBaseClass 
{ 
    private IServiceD _serviceD; 

    public MySubClassA (null, null, null, null) 
    public MySubClassA (IServiceA serviceA, IServiceB serviceB, 
         IServiceC serviceC, IServiceD serviceD) 
      : base (serviceA, serviceB, serviceC) 
    { 
     _serviceD = serviceD; 
    } 
} 

Il problema qui è che ho più sottoclassi, solo il 10 o giù di lì in questo momento, ma il numero aumenterà. Ogni volta che ho bisogno di aggiungere un'altra dipendenza alla classe base, devo passare attraverso ogni sottoclasse e aggiungere manualmente anche la dipendenza. Questo lavoro manuale mi fa pensare che c'è qualcosa di sbagliato nel mio design.

Quindi è possibile dichiarare il costruttore di MyBaseClassA senza i servizi richiesti dalla classe di base nel costruttore di sottoclassi? ad esempio, in modo che il costruttore di MyBaseClassA ha solo questo codice molto più semplice:

public MySubClassA (null) 
    public MySubClassA (IServiceD serviceD) 
    { 
     _serviceD = serviceD; 
    } 

Di cosa ho bisogno di cambiare nella classe base in modo che vi avviene l'iniezione di dipendenza e non ha bisogno di essere aggiunti alle classi secondarie come bene? Sto usando LightInject IoC.

+2

Impossibile che il contenitore IoC si inietti alle proprietà anziché al costruttore? Penso che sarebbe molto più semplice, specialmente se si sono classi profondamente annidate e molta iniezione. –

+2

In aggiunta all'altra idea di Simon .. puoi lanciare un wrapper attorno a queste dipendenze (un contenitore di dipendenze, se vuoi) come un'altra soluzione. Anche se preferisco l'approccio alla proprietà. –

+0

@SimonBelanger tutto il resto nell'app utilizza l'iniezione param. Penso che l'iniezione di proprietà sia stata provata in una parte dell'app e abbia avuto qualche problema che ho dimenticato - dovrò rivisitarla. –

risposta

14

Questo lavoro manuale mi fa pensare che c'è qualcosa di sbagliato nel mio design .

Probabilmente esiste. E 'difficile essere specifici con gli esempi che hai dato, ma spesso tale problema è causato da uno dei seguenti motivi:

  • si utilizza classi di base per implementare le preoccupazioni trasversali (come la registrazione, la gestione delle transazioni, i controlli di sicurezza , validazione, ecc) al posto dei decoratori.
  • Si utilizzano le classi di base per implementare il comportamento condiviso anziché posizionare tale comportamento in componenti separati che possono essere iniettati.

Le classi base vengono spesso abusate per aggiungere preoccupazioni trasversali alle implementazioni. In tal caso, la classe base diventa presto una God object: una classe che fa troppo e sa troppo. Viola lo Single Responsibility Principle (SRP), causandone il cambiamento frequente, la complessità e rendendo difficile il test.

La soluzione a questo problema è rimuovere la classe di base tutti insieme e utilizzare più decorators anziché una singola classe di base. Dovresti scrivere un decoratore per preoccupazione trasversale. In questo modo ogni decoratore è piccolo e concentrato e ha una sola ragione per cambiare (una singola responsabilità). Utilizzando a design that is based on generic interfaces è possibile creare decoratori generici in grado di racchiudere un intero set di tipi correlati architettonicamente. Ad esempio, un singolo decoratore che può avvolgere tutte le implementazioni del caso d'uso nel tuo sistema, o un decoratore che avvolge tutte le query nel sistema che ha un certo tipo di ritorno.

Le classi di base sono spesso abusi per contenere un insieme di funzionalità non correlate riutilizzate da più implementazioni. Invece di mettere tutta questa logica in una classe base (facendo diventare la classe base un incubo di manutenzione), questa funzionalità dovrebbe essere estratta in più servizi da cui le implementazioni possono dipendere.

Quando si inizia a refactoring verso tale disegno, vedrete spesso che le implementazioni iniziare a ricevere molte dipendenze, un anti-modello che si chiama costruttore sovrainiezione. La sovrainiezione del costruttore è spesso un segno che una classe viola l'SRP (rendendolo complesso e difficile da testare). Ma spostare la logica dalla classe base alle dipendenze non ha reso l'implementazione più difficile da testare, e infatti il ​​modello con la classe base aveva gli stessi problemi, ma con la differenza che le dipendenze erano nascoste.

Osservando attentamente il codice nelle implementazioni, si vedrà spesso una sorta di schema di codice ricorrente. Più implementazioni utilizzano lo stesso insieme di dipendenze allo stesso modo. Questo è un segnale che manca un'astrazione. Questo codice e le sue dipendenze possono essere estratti in un aggregate service. I servizi aggregati riducono la quantità di dipendenze di cui necessita un'implementazione e avvolgono il comportamento comune.

L'utilizzo dei servizi di aggregazione è simile al "wrapper" di cui parla @SimonWhitehead nei commenti, ma si noti che i servizi aggregati riguardano l'astrazione sia delle dipendenze che del comportamento. Se crei un "contenitore" di dipendenze ed esporti tali dipendenze tramite proprietà pubbliche per le implementazioni per utilizzarle, non stai abbassando il numero di dipendenze da cui dipende l'implementazione e non stai abbassando la complessità di tale classe e non stai facendo questa implementazione è più facile da testare. I servizi aggregati, d'altra parte, riducono il numero di dipendenze e la complessità di una classe, facilitando l'analisi e il test.

Quando si seguono queste regole, nella maggior parte dei casi non sarà necessario avere una classe base.

+0

+1 per introdurre preoccupazioni trasversali e soluzioni che usano i decoratori. (in realtà, dovrebbe +2) – Fendy

+1

risposta più sexy in tal modo –

+0

Ogni tanto penso che sarebbe una buona idea ripulire e creare "classe di Dio" - Poi mi imbatto in questa risposta, ancora una volta, e mi rendo conto che è uno stupido idea: D – ppumkin

Problemi correlati