Meriti di DI, recensione di implementazione di Abstract Factory

3

Sto scrivendo un nuovo sistema e sto cercando di rimanere fedele ai principi SOLID, in particolare Interfaccia e Iniezione delle dipendenze. Stiamo attaccando da vicino allo stack Microsoft, quindi stiamo utilizzando C # e il contenitore Unity. Sono in un negozio relativamente immaturo per quanto riguarda i concetti SOLIDI, quindi cerco di mantenere le cose il più semplici possibile per tutti, incluso me stesso.

Ho uno stile in cui preferisco utilizzare la funzione di costruzione per accettare i parametri di runtime e convalidare tutti i campi di cui l'oggetto potrebbe aver bisogno in anticipo. Ciò rende difficile l'iniezione basata su un semplice costruttore e mi ha guidato lungo il percorso astratto della fabbrica.

Ho anche una dipendenza in cui ho bisogno di un elenco di interfacce. Ancora una volta questo non era qualcosa che Unity sosteneva elegantemente e ancora una volta la fabbrica astratta sembrava giusta.

link link

Ho iniziato con idee elevate di una fabbrica astratta generica, ma ad essere onesti stavo passando un momento difficile a capire come ottenere le registrazioni dei container giuste. Se fosse stato difficile per me, sarebbe stato molto più difficile spiegarlo e venderlo alla mia squadra.

Così ho fatto un passo indietro e ho creato questo schema in cui ho un'interfaccia e un'interfaccia di fabbrica corrispondente per ciascuna delle mie dipendenze. La registrazione del contenitore diventa semplicissima.

_container.RegisterType<IChartFactory, ChartFactory>();
_container.RegisterType<IDocumentFactory, DocumentFactory>();
_container.RegisterType<IReportFactory, ReportFactory>();
_reportFactory = _container.Resolve<IReportFactory>();
_report = _reportFactory.CreateReport1(runTimeData);

Se dovessimo cambiare una dipendenza, implementeremo l'interfaccia sottostante con il minimo aggiunto di implementare anche la fabbrica corrispondente.

public class DocumentFactory : IDocumentFactory
{  
    public IDocument Create(string filePath)
    {
        return new Document(filePath);
    }
}

public interface IReportFactory
{
    IReport CreateReport1(object runTimeData);
    IReport CreateReport2(object runTimeData);
}

public class ReportFactory : IReportFactory
{
    IDocumentFactory _documentFactory;
    IChartFactory _chartFactory;

    public ReportFactory(IDocumentFactory documentFactory, IChartFactory chartFactory)
    {
        _documentFactory = documentFactory;
        _chartFactory = chartFactory;
    }

    public IReport CreateReport1(object runTimeData)
    {
        return new ConcreteReport1(runTimeData, _documentFactory, _chartFactory);
    }

    public IReport CreateReport2(object runTimeData)
    {
        return new ConcreteReport2(runTimeData, _documentFactory, _chartFactory);
    }
}

Lo trovo semplice da capire e da spiegare. Se la dipendenza cambia, l'implementazione di poche righe di codice per la factory sembra un piccolo prezzo da pagare rispetto alla modifica di ogni punto del sistema in cui il tipo viene aggiornato.

Altre dipendenze si stanno insinuando e tutte queste interfacce e fabbriche si sentono eccessive. Sono preoccupato per il pushback. Non sono sicuro che i meriti giustificano lo schema e non sono sicuro che lo stia facendo bene. Ogni pensiero è molto apprezzato!

    
posta grinder22 10.03.2016 - 18:53
fonte

1 risposta

1

Benvenuti nel mondo divertente di IoC. L'ho passato di recente da solo. Può essere un enorme cambiamento e un po 'frustrante.

Un paio di feedback:

  1. Non preoccuparti troppo di avere troppe interfacce e fabbriche. È uno shock, ma è così che viene comunemente fatto. Anche per me è stato un adattamento.

  2. Se gli sviluppatori del tuo team non sono abituati a IoC, potrebbero ricevere un piccolo codice di debugging frustrato, poiché non possono più utilizzare "Vai alla definizione" per tracciare dove va una chiamata (mostrerà solo loro l'interfaccia e non l'implementazione). Per rendere questo un po 'meno doloroso, ti consiglio di inserire composizione root in un luogo coerente (ad esempio, sempre in global.asax.cs), utilizza una convenzione di denominazione coerente (ad esempio InitContainers o qualcosa del genere) e assicurati che gli sviluppatori sappiano di guardarti lì.

  3. Sono un po 'preoccupato per questo commento:

I have a style in which I prefer to use the constructor to accept runtime parameters and validate all the fields the object might need up front.

Questo è un altro punto in cui è necessario regolare la tua mentalità. I costruttori non dovrebbero fare nulla (tranne accettare DI) . Questo perché il metodo Ctor è notoriamente difficile da testare in isolamento. Se devi assolutamente passare i parametri nel costruttore, devi solo memorizzarli in variabili membro e rimandarli usando qualsiasi cosa usando inizializzazione pigra .

  1. Non dimenticare, per le fabbriche, non vuoi crearne uno nuovo ogni volta. Con Unity, credo che useresti RegisterInstance o un Manager Lifestetime per controllare se Resolve<> restituirà una nuova istanza ogni volta o la stessa istanza per processo / thread / richiesta.
risposta data 11.02.2017 - 01:39
fonte

Leggi altre domande sui tag