Iniezione di dipendenza bilanciata con design API pubblico

10

Stavo pensando a come bilanciare la progettazione testabile usando l'iniezione di dipendenza con l'offerta di API pubbliche fisse semplici. Il mio dilemma è: le persone vorrebbero fare qualcosa come var server = new Server(){ ... } e non devono preoccuparsi di creare le molte dipendenze e il grafico delle dipendenze che potrebbe avere un Server(,,,,,,) . Durante lo sviluppo, non mi preoccupo troppo, perché utilizzo un framework IoC / DI per gestire tutto ciò (non sto utilizzando gli aspetti di gestione del ciclo di vita di qualsiasi contenitore, il che complicherebbe ulteriormente le cose).

Ora è improbabile che le dipendenze vengano nuovamente implementate. La componentistica in questo caso è quasi puramente per testabilità (e design decente!) Piuttosto che creare cuciture per l'estensione, ecc. Il 99,999% delle persone desidera utilizzare una configurazione predefinita. Così. Potrei hardcode le dipendenze. Non voglio farlo, perdiamo i nostri test! Potrei fornire un costruttore predefinito con dipendenze hard-coded e uno che prende le dipendenze. Questo è ... disordinato e probabilmente confuso, ma fattibile. Potrei fare in modo che la dipendenza riceva il costruttore interno e fare in modo che la mia unità collauda un'assemblea di amici (supponendo C #), che riordina l'API pubblica ma lascia una cattiva trappola nascosta in agguato per la manutenzione. Avere due costruttori che sono implicitamente connessi piuttosto che esplicitamente sarebbe un cattivo design in generale nel mio libro.

Al momento è il meno male che riesco a pensare. Opinioni? Saggezza?

    
posta kolektiv 05.01.2011 - 02:23
fonte

3 risposte

8

L'iniezione di dipendenza è un modello potente se usata bene, ma troppo spesso i suoi professionisti diventano dipendenti da qualche framework esterno. L'API risultante è piuttosto dolorosa per quelli di noi che non vogliono legare liberamente la nostra app insieme al nastro per i duct XML. Non dimenticare di Plain Old Objects (POO).

Innanzitutto considera come ti aspetteresti che qualcuno utilizzi la tua API, senza il framework in questione.

  • Come si crea un'istanza del server?
  • Come estendi il server?
  • Che cosa vuoi esporre nell'API esterna?
  • Che cosa vuoi nascondere nell'API esterna?

Mi piace l'idea di fornire implementazioni predefinite per i tuoi componenti e il fatto che tu abbia questi componenti. Il seguente approccio è una pratica OO perfettamente valida e decente:

public Server() 
    : this(new HttpListener(80), new HttpResponder())
{}

public Server(Listener listener, Responder responder)
{
    // ...
}

Finché si documentano le implementazioni predefinite per i componenti nei documenti API e si fornisce un costruttore che esegue tutta l'installazione, si dovrebbe andare bene. I costruttori a cascata non sono fragili finché il codice di installazione si verifica in un costruttore principale.

In sostanza, tutti i costruttori che si affacciano pubblicamente avrebbero le diverse combinazioni di componenti che si desidera esporre. Internamente, inseriscono i valori predefiniti e si rinvia a un costruttore privato che completa l'installazione. In sostanza, questo ti fornisce alcuni DRY ed è abbastanza facile da testare.

Se devi fornire l'accesso friend ai tuoi test per configurare l'oggetto Server per isolarlo per il test, provaci. Non farà parte dell'API pubblica, che è ciò con cui vuoi stare attento.

Sii gentile con i tuoi utenti API e non richiede un framework IoC / DI per utilizzare la tua libreria. Per avere un'idea del dolore che stai infliggendo assicurati che i tuoi test unitari non facciano affidamento sul framework IoC / DI. (È un vero cruccio personale, ma non appena introduci il framework non è più un test unitario: diventa test di integrazione).

    
risposta data 05.01.2011 - 03:06
fonte
2

Che ne dici di fornire una sottoclasse che fornisce la "API pubblica"

class StandardServer : Server
{
    public StandardServer():
        this( depends1, depends2, depends3)
    {
    }
}

L'utente può new StandardServer() ed essere sulla buona strada. Possono anche utilizzare la classe base Server se desiderano un maggiore controllo sul funzionamento del server. Questo è più o meno l'approccio che uso. (Non uso un framework perché non ho ancora visto il punto.)

Questo espone ancora la API interna, ma penso che dovresti. Hai diviso i tuoi oggetti in diversi componenti utili che dovrebbero funzionare in modo indipendente. Non si sa quando una terza parte potrebbe voler utilizzare uno dei componenti in isolamento.

    
risposta data 05.01.2011 - 07:41
fonte
0

I could make the dependency receiving constructor internal and make my unit tests a friend assembly (assuming C#), which tidies the public API but leaves a nasty hidden trap lurking for maintenance.

Questa non mi sembra una "cattiva trappola nascosta". Il costruttore pubblico dovrebbe chiamare solo quello interno con le dipendenze "predefinite". Finché è chiaro che il costruttore pubblico non deve essere modificato, tutto dovrebbe andare bene.

    
risposta data 05.01.2011 - 02:53
fonte

Leggi altre domande sui tag