2012-07-04 7 views
13

Eventuali duplicati:
Interface vs Base classVantaggio di utilizzare l'interfaccia sulla classe astratta per il modello di repository?

Il suo comune per vedere il modello repository implementato utilizzando interfacce

public interface IFooRepository 
{ 
    Foo GetFoo(int ID); 
} 

public class SQLFooRepository : IFooRepository 
{ 
    // Call DB and get a foo 
    public Foo GetFoo(int ID) {} 
} 

public class TestFooRepository : IFooRepository 
{ 
    // Get foo from in-memory store for testing 
    public Foo GetFoo(int ID) {} 
} 

Ma si potrebbe ugualmente farlo utilizzando le classi astratte.

public abstract class FooRepositoryBase 
{ 
    public abstract Foo GetFoo(int ID); 
} 

public class SQLFooRepository : FooRepositoryBase 
{ 
    // Call DB and get a foo 
    public override Foo GetFoo(int ID); {} 
} 

public class TestFooRepository : FooRepositoryBase 
{ 
    // Get foo from in-memory store for testing 
    public override Foo GetFoo(int ID); {} 
} 

Quali sono i vantaggi specifici di utilizzare un interfaccia su una classe astratta in uno scenario repository?

(vale a dire non solo dirmi che è possibile implementare più interfacce, so che questo già - perché l'hai fatto in un'implementazione repository)

Modifica per chiarire - pagine come " MSDN - Choosing Between Classes and Interfaces "può essere parafrasato come 'Scegli classi oltre le interfacce a meno che non ci sia una buona ragione per non' - quali sono le buone ragioni nel caso specifico di un modello Repository

+0

non utilizzando un'interfaccia a prevenire la boxe e l'inefficienza questo porta? – Liam

+1

Una scelta di design. –

+0

@Pranay - dove va la tua risposta? : – Ryan

risposta

3

Il vantaggio principale di utilizzare un'interfaccia su una classe astratta in questa istanza è che un'interfaccia è completamente trasparente: si tratta più di un problema in cui non si ha accesso all'origine della classe che si sta ereditando da .

Tuttavia, questa trasparenza consente di produrre test di unità di un ambito noto: se si verifica una classe che accetta un'interfaccia come parametro (utilizzando il metodo di iniezione delle dipendenze), si sa che si sta testando la classe con un noto quantità; l'implementazione di test dell'interfaccia conterrà solo il codice di test.

Analogamente, durante il test del repository, sai che stai testando solo il tuo codice nel repository. Questo aiuta a limitare il numero di possibili variabili/interazioni nel test.

2

mentre altri possono avere altro da aggiungere, da un puramente pratico Al punto di vista, la maggior parte dei framework IoC funziona meglio con interface -> class mapping. Puoi avere diverse visioni sulle tue interfacce delle classi &, mentre con l'ereditarietà le visibilità devono corrispondere.

Se non si utilizza un framework IoC, dal mio punto di vista non c'è differenza. I provider si basano su classi base astratte.

4

Personalmente, tendono ad avere un'interfaccia che contiene la firma per i metodi che sono puramente "connessi alle imprese", per esempio Foo GetFoo(), void DeleteFood(Foo foo), ecc ho anche una classe astratta generica che tiene protetto metodi come T Get() o void Delete(T obj) .

tengo i miei metodi protetti in astratto Repository classe in modo che il mondo esterno non è a conoscenza del plumbery (Repository sarà simile object), ma solo del modello di business attraverso l'interfaccia.

In cima avere il plumbery condiviso un altro vantaggio è che ho per esempio una Delete metodo (protected) a disposizione di qualsiasi repository, ma non è pubblica, quindi non sono costretto a implementare su un archivio in cui non ha alcuna attività che significa eliminare qualcosa dalla mia fonte di dati.

public abstract class Repository<T> 
{ 
    private IObjectSet objectSet; 

    protected void Add(T obj) 
    { 
     this.objectSet.AddObject(obj); 
    } 

    protected void Delete(T obj) 
    { 
     this.objectSet.DeleteObject(obj); 
    } 

    protected IEnumerable<T>(Expression<Func<T, bool>> where) 
    { 
     return this.objectSet.Where(where); 
    } 
} 

public interface IFooRepository 
{ 
    void DeleteFoo(Foo foo); 
    IEnumerable<Foo> GetItalianFoos(); 
} 

public class FooRepository : Repository<Foo>, IFooRepository 
{ 
    public void DeleteFoo(Foo foo) 
    { 
     this.Delete(foo); 
    } 

    public IEnumerable<Foo> GetItalianFoos() 
    { 
     return this.Find(foo => foo.Country == "Italy"); 
    } 
} 

Il vantaggio di utilizzare la classe astratta su un'interfaccia per il plumbery è che il mio repository di cemento non devono implementare il metodo non hanno bisogno (o DeleteAdd per esempio), ma sono a loro disposizione, se ne hanno bisogno. Nel contesto attuale, non vi sono ragioni commerciali per alcuni Foos, quindi il metodo non è disponibile nell'interfaccia.

Il vantaggio di utilizzare un'interfaccia su una classe astratta per il modello di business è che l'interfaccia fornisce le risposte a come ha senso manipolare Foo da un lato aziendale (ha senso eliminare alcuni foos? ? eccetera.). È anche più facile usare questa interfaccia durante il test dell'unità. L'abstract Repository che uso non può essere testato unitamente perché di solito è strettamente accoppiato con il database. Può essere testato solo con l'integrazione testata. Usare una classe astratta per lo scopo commerciale dei miei repository mi impedirà di usarli nei test unitari.

+0

Ma perché? l'implementazione (plumbing) può essere nascosta usando uno dei due metodi. – Ryan

+0

Il plumbery è condiviso da tutti i repository (utilizzando la stessa origine dati). Quindi tutti i repository ereditano dalla stessa classe astratta 'Repository'. Il plumbery è scritto una volta. L'interfaccia ha uno scopo aziendale specifico. È implementato solo il repository che gestisce questo scopo. Di solito non hai 50 fonti di dati, ma potresti avere 50 obiettivi lavorativi. È il mio modo di fare perché penso che sia comodo e pulito, ma come tutto ci sono diversi modi di fare. – Guillaume

+0

Quindi, se ti capisco dal tuo esempio di cancellazione, stai dicendo che preferisci le classi astratte sulle interfacce per un repository? – Ryan

2

Immagino che la differenza chiave sarebbe che una classe astratta può contenere proprietà private & metodi, in cui un'interfaccia non può, in quanto è solo un semplice contratto.

Il risultato è un'interfaccia sempre "niente shenigans qui - quello che vedi è ciò che ottieni" mentre una classe base astratta può consentire effetti collaterali.

+0

Perché ciò è importante per un repository o qualsiasi altra cosa. L'idea di incapsulamento è che fa qualcosa senza che tu debba preoccuparti di come - se ci sono imbrogli in corso allora forse è per una buona ragione e non dovresti sapere o preoccupartene (vedi casi in cui hai bisogno di so - ma questa è un'astrazione che perde) – Ryan

+0

Bene, le interfacce e le classi astratte risolvono entrambi un problema simile "Definire un contratto comune" - personalmente preferisco le interfacce, ma solo per la ragione sopra specificata, e forse perché le definizioni sembrano solo più ordinate. Potrebbe essere una di quelle cose che è semplicemente un gusto personale. –

3

Questa è una domanda generale che si applica a qualsiasi gerarchia di classi, non solo agli archivi. Da un punto di vista OO puro, un'interfaccia e una classe astratta pura sono uguali.

Se la classe fa parte di un'API pubblica, il vantaggio principale dell'uso di una classe astratta è che è possibile aggiungere metodi in futuro con il minimo rischio di rompere le implementazioni esistenti.

Alcune persone preferiscono definire un'interfaccia come "qualcosa che una classe può fare" e una classe di base come "che cos'è una classe", e quindi utilizzerà solo le interfacce per le funzionalità periferiche e definirà sempre la funzione primaria (ad es. . repository) come classe. Non sono sicuro di dove mi trovi.

Per rispondere alla tua domanda, non penso ci sia alcun vantaggio nell'utilizzare un'interfaccia quando definisce la funzione primaria della classe.

1

Dai un'occhiata all'implementazione di Tim McCarthy's Repository Framework. < http://dddpds.codeplex.com/>

Egli usa interfacce come IRepository<T> per la definizione dei contratti, ma usa anche le classi astratte come RepositoryBase<T> o il suo SqlCeRepositoryBase <T> che implementa IRepository<T>. La classe base astratta è il codice per eliminare molto codice pubblicitario. Un repository specifico per tipo deve solo ereditare la classe base astratta e deve aggiungere il codice per il suo scopo. Gli utenti dell'API possono semplicemente codificare l'interfaccia per contratto.

Quindi è possibile combinare entrambi gli approcci per sfruttarne i vantaggi.

Inoltre, penso che la maggior parte dei framework IoC sia in grado di gestire classi astratte.

2

Dal momento che il modello ha origine nel Domain Driven Design, ecco una risposta DDD:

Il contratto di un repository è di solito definito nello strato di dominio. Ciò consente agli oggetti nei livelli Dominio e Applicazione di manipolare le astrazioni dei Repository senza preoccuparsi della loro reale implementazione e dei dettagli di archiviazione sottostanti - in altre parole, essere ignoranti per la persistenza. Inoltre, spesso vogliamo che i comportamenti specifici siano inclusi nei contratti di alcuni repository (oltre a Van(), GetById(), ecc.) Quindi preferisco il modulo ISomeEntityRepository che solo IRepository<SomeEntity> - vedremo perché hanno bisogno per essere interfacce più tardi.

Le implementazioni concrete dei repository, d'altra parte, risiedono nel livello Infrastruttura (o nel modulo Test per i repository di test). Implementano il suddetto contratto di deposito ma hanno anche una propria gamma di caratteristiche specifiche di persistenza. Ad esempio, se stai utilizzando NHibernate per mantenere le tue entità, avere una superclasse in tutti i repository di NHibernate con la sessione di NHibernate e altri impianti idraulici generici relativi a NHibernate potrebbero tornare utili.

Poiché non è possibile ereditare più classi, una di queste 2 cose ereditate dal repository concreto finale deve essere un'interfaccia.

E 'più logico per il contratto strato di dominio da un'interfaccia (ISomeEntityRepository) dal momento che è un'astrazione puramente dichiarativa e non deve fare alcuna ipotesi su ciò che verrà utilizzato meccanismo di persistenza sottostante - cioè non deve implementare nulla .

L'uno specifico per la persistenza può essere una classe astratta (NHibernateRepository o NHibernateRepository<T> nello strato Infrastructure), che consente di centralizzare lì alcuni comportamenti che sono comuni a tutta la gamma di repository specifici persistente-store che esisteranno.

Questo si traduce in qualcosa di simile a:

public class SomeEntityRepository : NHibernateRepository<SomeEntity>, ISomeEntityRepository 
{ 
    //... 
} 
Problemi correlati