2009-05-05 13 views
24

Una cosa che vedo in alcune app aziendali DDD su cui lavoro è l'uso di interfacce identiche alle entità di dominio, con una mappatura one-to-one di proprietà e funzioni. Infatti, un oggetto dominio viene sempre utilizzato attraverso la sua interfaccia one-to-one e tutte le entità dominio hanno un'interfaccia one-to-one in questo stile.Le interfacce one-to-one con entità di dominio sono buone o cattive? Perché?

Ad esempio:

oggetto Dominio Account:

public class Account : IAccount 
{ 
    public string Name {get;set;} 
    //...some more fields that are also in IAccount 
    public decimal Balance {get;set;} 
} 

Ed è corrispondenza interfaccia

public interface IAccount 
{ 
    string Name {get;set;} 
    //... all the fields in Account 
    decimal Balance {get;set;} 
} 

Ma ultimamente sono diventato sempre più convinto che questo è, infatti, un anti -modello.
L'ho gestito da alcuni architetti della comunità open source, e dicono che questo si basa su errori di progettazione o difetti, da qualche parte sulla catena del design.

Quindi dico ai miei colleghi che dovrebbero smettere di creare interfacce per gli oggetti Dominio. Perché non c'è scopo per loro e devi aggiornare l'interfaccia ogni volta che aggiorni le entità di dominio.

Prima si affermava che queste interfacce fornivano il "disaccoppiamento", ma io contromano perché le interfacce hanno una relazione uno-a-uno con le entità di dominio che in realtà non forniscono alcun disaccoppiamento, una modifica all'interfaccia significa un cambiamento nell'entità del dominio e viceversa.

La prossima affermazione è che abbiamo bisogno delle interfacce a scopo di test. Il mio contatore è che Rhino-mock prevede il beffeggiamento e lo stub delle classi concrete. Ma sostengono che Rhino-mock ha problemi con lezioni concrete. Non so se lo compro, anche se i rinoceronti hanno problemi con le classi concrete, questo non significa necessariamente che dovremmo usare le interfacce per le entità di dominio.

Quindi sono curioso:

Perché si hanno interfacce one-to-one per i vostri entità del dominio?

Perché no?

Perché è una buona o cattiva pratica?

Grazie per la lettura!

EDIT: Devo notare che uso sempre le interfacce e credo che, se richiesto, userò un'interfaccia in un batter d'occhio. Ma mi riferisco in particolare alle entità di dominio con interfacce one-to-one.

+0

AFAIK Rhino Mock non può simulare i metodi di classe concreti a meno che non siano sovrascrivibili. Quindi dovresti rendere virtuali tutti i metodi degli oggetti di dominio. Non bene. –

+0

Correlati: http://stackoverflow.com/questions/2659366/java-interfaces-methodology-should-every-class-implement-an-interface – jaco0646

risposta

8

Si tratta di una cattiva pratica, come descritto, ma ...

Non c'è alcun motivo specifico che le interfacce devono essere diverso da quello tuoi entità del dominio; a volte è davvero la mappatura giusta. Ma è sospetto che sia sempre così. Il punto di preoccupazione è la questione se le interfacce siano state progettate o meno, o se siano state semplicemente gettate in posizione per mancanza di tempo/pigrizia.

Per utilizzare l'esempio, l'interfaccia IAccount che descrivi espone getter e setter sull'oggetto Account; sembra un po 'strano e improbabile che tutto ciò che utilizza un Account abbia la necessità di impostare il saldo sull'account e che quella autorizzazione implicita sia specificata a quel livello di interfaccia.Non c'è posto nel tuo sistema in cui vuoi semplicemente controllare ma non impostare il saldo del Conto?

+0

Sì, ci sono legioni di aree in cui non viene utilizzata l'intera interfaccia. In realtà è più il caso che una parte dell'interfaccia non è usata, che è. –

+1

Sì, le app non sono progettate da domini; è puro design basato sui dati. E sembra che sia un design molto povero, per quello. –

+0

Sono purtroppo d'accordo ... –

7

Il motivo principale per specificare sempre gli oggetti di dominio come interfacce anziché direttamente come classi è quello di darti un certo grado di libertà nell'implementazione. Nel tuo esempio hai solo un tipo di IAccount, quindi è un po 'ridondante.

Ma cosa succede se si ha, ad esempio:

public class Account : IAccount { ... }  // Usual account, persistent 
public class MockAccount : IAccount { ... } // Test mock object 
public class TransAccount : IAccount { ... } // Account, not persistent 
public class SimAccount : IAccount { ... } // Account in a performance sim 

e così via?

Definendo gli oggetti dominio come interfacce, è possibile sostituire le implementazioni senza disturbare la definizione del dominio.

+0

Ergo la frase che inizia "In questo esempio ...". Ma quello che farai per implementare l'interfaccia è certamente un luogo di cambiamento, e una proprietà desiderabile di un progetto OO è di avere "punti cardine" nei probabili luoghi di cambiamento. A proposito, in realtà ero solito discutere questa domanda nello stesso modo in cui tu sei, e sono stato convertito quando volevo iniziare a prendere in giro alcune cose per i test. –

+0

+1 questo sembra sciocco fino a due anni in manutenzione su un'applicazione monolitica ed è necessario implementare una modifica per supportare nuove varianti senza interrompere strati di dipendenze. Inoltre, TDD è un gioco da ragazzi. –

+0

Se già conosci la risposta, perché stai facendo la domanda? –

5

In generale, se le mie classi non faranno parte di un modello di progettazione come Strategia o Visitatore, non aggiungo interfacce.

L'aggiunta di interfacce è davvero utile per modelli di progettazione come Strategy e Visitor, ma in quei casi non copio i getter e i setter delle classi di dominio. Invece, creo interfacce specifiche per le interfacce di pattern di progettazione che creo.

interface SomeStrategy { 
    void doSomething(StrategyData data); 
} 

interface StrategyData { 
    String getProperty1(); 

    String getProperty2(); 
} 

Ciò consente di consentire alle classi di dominio di implementare tali interfacce o di utilizzare il modello dell'adattatore. Trovo che questo sia un approccio molto più pulito che crea interfacce solo per il gusto di farlo.

La progettazione deve sempre ridurre l'incertezza. Creare interfacce per il suo scopo non riduce l'incertezza, anzi probabilmente aumenta la confusione poiché non ha alcun senso.

+0

Buona risposta, grazie –

3

One-to-One Interfacce sul entità sono un anti-modello

James Gregory messo meglio di me here.

+0

Ottima risposta, grazie per il link! –

+0

Ovviamente il dominio Anemic è il vero problema. Ma questo quando ne hai già uno. Puoi almeno creare un isolatore tra il depender e l'implementazione in modo da poter cambiare con meno mal di testa. – jeremyjjbrown

0

Perché questo ha esaurito?

Trovo di avere un'interfaccia per un oggetto di dominio, come ha detto Charlie Martin, mi consente di scegliere la mia implementazione.

Un esempio fondamentale è l'identificatore (object.Id) per un oggetto, questo sarà diverso a seconda di dove si memorizza quell'oggetto e la responsabilità per crearla potrebbe o meno riposare nell'implementazione dei dati in un secondo momento. In SQL Server si potrebbe ottenere un numero autonumerico, ma nella memoria della tabella di Azure si potrebbe optare per una guida, ma non si desidera modificare la logica aziendale dell'app perché si cambia la posizione in cui si memorizzano i dati.

È possibile o meno scegliere di mantenere il mio oggetto dominio persistente o addirittura di utilizzarlo nel livello di presentazione, a seconda dell'ambito della mia app che è il migliore. Ma aggiungere una serie di interfacce di dominio comuni in un livello comune mi consente di scrivere servizi contro di loro e riutilizzarli ancora e ancora.

Vorremmo andare oltre la stessa argomentazione su quale dovrebbe essere un indirizzo se non avessimo IAddress, i nuovi programmatori potrebbero riscrivere roba per le carte di credito se non fosse per ICreditCard.

L'etichetta anti-pattern è un cattivo uso del linguaggio, è una semplificazione eccessiva per descrivere il valore delle soluzioni per compiti complessi e vari.

C'è un posto per la maggior parte dei modelli anche il singleton denigrato, il che significa che non è un "anti-pattern", almeno per quanto suggerisce il termine.

+0

Sicuramente, è necessario utilizzare i generici, non le interfacce, per astrarre la differenza di tipo per l'ID oggetto, a meno che la chiave stessa non sia stata modellata sul tipo più debole (ad es. Stringa). – StuartLC

+0

Io uso una stringa ogni volta, diventa troppo complicato se usi i generici, devi dire ai tuoi servizi circa il generico nel modello che passa, e se ha una proprietà secondaria che è un'entità generica e il servizio è generico - Mi fai smettere –

1

Sono d'accordo con te. Le interfacce dovrebbero agire come un contratto, quindi non vi è alcun valore dall'avere interfacce one-to-one con le entità di dominio. Potrebbe essere utile se desideri astrarre un determinato comportamento. Ma questo funzionerà in alcuni casi.

Problemi correlati