2015-01-23 7 views
10

Ich mache einige Nachforschungen über SOLID Principal und fand einige Probleme in Implementierungen von Repository-Muster. Ich werde jedes Problem erklären, bitte korrigieren Sie mich, wenn ich falsch liege.Befolgt das Repository-Muster SOLID-Prinzipien?

Problem 1

Repository Pattern Breaks Einzel Verantwortung Prinzip (S)

Let sagen wir eine Schnittstelle haben, die als

definieren
public interface IRepository<T> where T: IEntity 
{ 

    IEnumerable<T> List { get; } 
    void Add(T entity); 
    void Delete(T entity); 
    void Update(T entity); 
    T FindById(int Id); 
} 

Klar es verletzt das Prinzip der einheitlichen Verantwortung, weil w Wenn wir diese Schnittstelle implementieren, setzen wir in einer einzigen Klasse sowohl Command als auch Query ein. und das wird nicht erwartet.

Problem 2

Repository Pattern Breaks Schnittstelle Segregation Prinzip (I)

sagen, wir haben 2 Umsetzung der oben Schnittstelle.

Erste Implementierung

CustomerRepository : IRepository<Customer> 
{ 
    //All Implementation 
} 

Zweite Implementierung

ProductRepository : IRepository<Product> 
{ 
    //All Implementation except Delete Method. So Delete Method Will be 
    void Delete (Product product){ 
     throw Not Implement Exception! 
    } 
} 

Und laut ISP "soll kein Kunde gezwungen werden, auf Methoden angewiesen es nicht verwenden." So haben wir gesehen, dass es eindeutig auch gegen den ISP verstößt.

So ist mein Verständnis Repository Muster nicht SOLID Haupt folgen. Was denken Sie? Warum sollten wir diese Art von Mustern wählen, die gegen den Auftraggeber verstoßen? Brauchen Sie Ihre Meinung?

Antwort

21

Offensichtlich verletzt es das Prinzip der einfachen Verantwortlichkeit, denn wenn wir diese Schnittstelle implementieren, setzen wir in einer einzigen Klasse sowohl Befehl als auch Abfrage. und das wird nicht erwartet.

Das ist nicht das Prinzip der einfachen Verantwortung. SRP bedeutet, dass die Klasse ein Hauptanliegen haben sollte. Die Hauptaufgabe eines Repositorys besteht darin, "zwischen den Domänen- und Datenmapping-Schichten zu vermitteln, indem eine sammlungähnliche Schnittstelle für den Zugriff auf Domänenobjekte verwendet wird" (Fowler). Das macht diese Klasse.

Repository Pattern Breaks Schnittstelle Segregation Prinzip

Wenn das stört, dann einfach eine andere Schnittstelle zur Verfügung stellen, die nicht die Methode enthält Sie nicht implementieren gehen. Ich persönlich würde das nicht tun; es gibt eine Menge zusätzlicher Schnittstellen für den Grenznutzen, und es bringt die API unnötig durcheinander.A NotImplementedException ist sehr selbsterklärend.

Sie werden feststellen, dass es viele Regeln, Gesetze oder Prinzipien in der Informatik gibt, die Ausnahmen haben, und einige, die völlig falsch sind. Umarmen Sie die Mehrdeutigkeit, lernen Sie, Software aus einer praktischeren Perspektive zu schreiben, und hören Sie auf, über Software-Design in solchen absoluten Begriffen nachzudenken.

+0

Dank @Robert, Sehr hilfreich. –

+0

@AnkitSarkar Das 'IRepository ' ist ein Anti-Pattern für sich, wenn es nicht korrekt verwendet wird. http://codebetter.com/gregyoung/2009/01/16/ddd-the-generic-repository/ – plalx

4

Offensichtlich verletzt es die einzige Verantwortung Prinzip

Es ist nur klar, wenn man eine sehr enge Definition von dem, was die SRP ist. Tatsache ist, SOLID verletzt SOLID. Die Prinzipien selbst widersprechen sich. SRP steht im Gegensatz zu DRY, da Sie sich oft wiederholen müssen, um Probleme zu trennen. LSP ist in einigen Situationen mit ISP uneins. OCP steht oft in Konflikt mit DRY und SRP. Diese Grundsätze sind hier nicht so hart und schnell Regeln, aber um Sie zu führen ... versuchen Sie, sich an sie zu halten, aber behandeln Sie sie nicht als Gesetze, die nicht gebrochen werden können.

Darüber hinaus verwirren Sie das Repository-Architekturmuster mit einem sehr spezifischen generischen Repository-Implementierungsmuster. Beachten Sie, dass sich ein generisches Repository von einem konkreten Repository unterscheidet. Es ist auch nicht erforderlich, dass ein Repository die von Ihnen genannten Methoden implementiert.

Ja, Sie können Befehl und Abfrage als zwei separate Probleme trennen, aber es gibt keine Anforderung, dass Sie dies tun, um jeder eine einzelne Verantwortung zu machen. Die Kommandoabfragetrennung ist ein nettes Prinzip, aber nicht etwas, das von SOLID abgedeckt wird, und sicherlich gibt es keinen Konsens darüber, ob die Trennung der Anliegen unter die vorherigen verschiedenen Verantwortlichkeiten fällt oder nicht. Sie sind mehr wie verschiedene Aspekte der gleichen Verantwortung. Sie könnten dies auf eine lächerliche Ebene bringen, wenn Sie wollten und behaupten, dass das Aktualisieren eine andere Verantwortung als das Löschen ist oder dass das Abfragen nach der ID eine andere Verantwortung als die Abfrage nach dem Typ oder was auch immer ist. Irgendwann musst du Linien zeichnen und Dinge einkleiden, und für die meisten Menschen ist das "Lesen und Schreiben einer Entität" eine einzige Verantwortung.

Repository Pattern Breaks Schnittstelle Segregation Prinzip

Zuerst Sie sind verwirrend Liskov Substitution Haupt mit Schnittstelle Segregationsprinzip. LSP ist, was durch Ihr Beispiel verletzt wird.

Wie bereits erwähnt, gibt es keine Anforderung, dass das Repository einen bestimmten Satz von Methoden außer einer "sammlungähnlichen Schnittstelle" implementiert. In der Tat wäre es durchaus akzeptabel sein, es so zu implementieren:

public interface IRepository<T> where...[...] {IEnumerable<T> List { get; }} 
public interface CustRepository : IRepository<Customer>, IRepoAdd, IRepoUpdate, IRepoDelete, IRepoFind {} 

Nun kann es gegebenenfalls keines der anderen Mitglieder implementieren, ohne LSP zu brechen, obwohl es eine ziemlich dumme Implementierung ist und man würde ich sicherlich nicht nur implementieren um zu vermeiden, LSP zu brechen.

Tatsache ist, es gibt wahrscheinlich keinen guten Grund, warum Sie ein Repository ohne zu löschen wünschen. Der einzige mögliche Grund, den ich mir vorstellen kann, wäre ein Read-Only-Repository, das ich eine separate Schnittstelle für die Verwendung einer schreibgeschützten Auflistungsschnittstelle definieren würde.

2

Ich benutze das Repository-Muster selbst und ich verwendete das Muster, um sicherzustellen, dass alle erforderlichen Schnittstellen implementiert sind. Dazu habe ich für alle Aktionen eigene Schnittstellen erstellt (IEntityCreator, IEntityReader, IEntityUpdater, IEntityRemover) und die Weiterleitung all dieser Schnittstellen veranlasst. So kann ich alle Methoden in einer konkreten Klasse implementieren und trotzdem alle Schnittstellen getrennt nutzen. Ich sehe keinen Grund zu behaupten, dass das Repository-Muster die SOLID-Prinzipien verletzt.Sie müssen lediglich die "Verantwortung" des Repositories korrekt definieren: Die Verantwortung des Repositories ist es, allen Zugriff auf die Daten des Typs T zu erleichtern. Das ist alles, was zu sagen ist. Wie oben erwähnt, habe ich auch eine schreibgeschützte Repository-Schnittstelle namens ReferenceRepository<T>, die nur die IEntityReader<T> Schnittstelle enthält. Alle Schnittstellen sind für das schnelle Kopieren unten definiert :) Darüber hinaus habe ich auch einige konkrete Klassen inklusive Caching und/oder Logging erstellt. Dies ist erforderlich, um alle weiteren erforderlichen Maßnahmen zu berücksichtigen, wie in der I in SOLID angegeben. Der Typ IEntity wird als Markerschnittstelle verwendet, um nur Entitäten und nicht irgendeine andere Art von Objekt zuzulassen (Sie müssen irgendwo anfangen).

/// <summary> 
/// This interface defines all properties and methods common to all Entity Creators. 
/// </summary> 
/// <typeparam name="TEntity">The type of the entity.</typeparam> 
public interface IEntityCreator<TEntity> 
    where TEntity : IEntity 
{ 
    #region Methods 
    /// <summary> 
    /// Create a new instance of <see cref="TEntity"/> 
    /// </summary> 
    /// <returns></returns> 
    TEntity Create(); 
    #endregion 
} 

/// <summary> 
/// This interface defines all properties and methods common to all Entity Readers. 
/// </summary> 
/// <typeparam name="TEntity">The type of the entity.</typeparam> 
public interface IEntityReader<TEntity> 
    where TEntity : IEntity 
{ 
    #region Methods 
    /// <summary> 
    /// Get all entities in the data store. 
    /// </summary> 
    /// <returns></returns> 
    IEnumerable<TEntity> GetAll(); 

    /// <summary> 
    /// Find all entities that match the expression 
    /// </summary> 
    /// <param name="whereExpression">exprssion used to filter the results.</param> 
    /// <returns></returns> 
    IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> whereExpression); 
    #endregion 
} 

/// <summary> 
/// This interface defines all properties and methods common to all Entity Updaters. 
/// </summary> 
/// <typeparam name="TEntity">The type of the entity.</typeparam> 
public interface IEntityUpdater<TEntity> 
    where TEntity : IEntity 
{ 
    #region Methods 
    /// <summary> 
    /// Save an entity in the data store 
    /// </summary> 
    /// <param name="entity">The entity to save</param> 
    void Save(TEntity entity); 
    #endregion 
} 

/// <summary> 
/// This interface defines all properties and methods common to all Entity removers. 
/// </summary> 
/// <typeparam name="TEntity">The type of the entity.</typeparam> 
public interface IEntityRemover<TEntity> 
    where TEntity : IEntity 
{ 
    /// <summary> 
    /// Delete an entity from the data store. 
    /// </summary> 
    /// <param name="entity">The entity to delete</param> 
    void Delete(TEntity entity); 

    /// <summary> 
    /// Deletes all entities that match the specified where expression. 
    /// </summary> 
    /// <param name="whereExpression">The where expression.</param> 
    void Delete(Expression<Func<TEntity, bool>> whereExpression); 
} 

/// <summary> 
/// This interface defines all properties and methods common to all Repositories. 
/// </summary> 
public interface IRepository { } 

/// <summary> 
/// This interface defines all properties and methods common to all Read-Only repositories. 
/// </summary> 
/// <typeparam name="TEntity">The type of the entity.</typeparam> 
public interface IReferenceRepository<TEntity> : IRepository, IEntityReader<TEntity> 
    where TEntity : IEntity 
{ 

} 

/// <summary> 
/// This interface defines all properties and methods common to all Read-Write Repositories. 
/// </summary> 
public interface IRepository<TEntity> : IReferenceRepository<TEntity>, IEntityCreator<TEntity>, 
    IEntityUpdater<TEntity>, IEntityRemover<TEntity> 
    where TEntity : IEntity 
{ 

} 
+0

PS. Ich habe einige andere Funktionen in vererbten Interfaces für Business Entities hinzugefügt (Enties mit ID), aber ich habe das weggelassen, um eine allgemeinere Lösung zu erstellen –

Verwandte Themen