2009-03-04 9 views
0

Ich habe eine Klasse, zu der ich ständig hinzufügen.Eine Klasse folgen OCP - Factoring-Funktionen in Objekte

Es ist mir aufgefallen, dass diese Klasse nicht Open-Closed ist, weil all diese neuen Features hinzugefügt werden. Also dachte ich über das Schließen dieser Klasse gegen diese Änderung nach, indem ich diese Funktionen in Request-Objekte einkapselte. Ich am Ende mit etwas wie:

public abstract class RequestBase{} 

public class AddRequest : RequestBase{} 

etc... 

public class OrderRepository{ 
    public void ProcessRequest(RequestBase request){} 
} 

Das macht OrderRepository offen für Erweiterungen und geschlossen für Änderungen. Jedoch lief ich schnell in ein paar Probleme mit diesem:

1.) Die Daten die diese Anfrage benötigt, um zu arbeiten ist sowohl vom Benutzer zur Verfügung gestellt (Laufzeit) und Abhängigkeitsinjektion geliefert. Ich kann offensichtlich nicht beide mit einem Konstruktor zufriedenstellen. Ich kann nicht tun:

public class AddRequest{ 
    public AddRequest(IEnumerable<Order> orders, int UserSuppliedContextArg1, DependencyInjectionArg1, DependencyInjectionArg2); 
} 

und rufen Sie das. Ich würde einen Weg für den DI-Rahmen haben wollen, um ein Objekt für mich "teilweise" zu konstruieren, und lass mich den Rest machen. Ich sehe jedoch keinen Weg, dies zu tun. Ich sah einen Blog, der dieses Konzept "variable Konstruktorinjektion" nannte.

2.) Das nächste, worüber ich nachdachte, war, dieses in zwei separate Klassen aufzuteilen. Der Benutzer würde einen RequestContext erstellen und füllen und ihn dann an das Repository übergeben, wodurch ein RequestProcessor (kann sich keinen besseren Namen vorstellen) daraus erstellt wird. Ich dachte darüber nach:

public abstract class RequestContextBase<T> where T : RequestProcessorBase{} 

public class AddRequestContext : RequestContextBase<AddRequestProcessor> 

public class OrderRepository{ 
    public void ProcessRequest<T>(RequestBase<T> request){ 
     var requestProcessor = IoC.Create<T>(); 
    } 
} 

und das war ein guter erster Schritt. Der Anforderungsprozessor benötigt jedoch den genauen Kontexttyp, den er speichert, den ich hier nicht habe. Ich könnte ein Wörterbuch der Typen Typen verwenden, aber das Niederlagen der Zweck des Open-Closed .So ist ich am Ende mit etwas wie zu tun:

public class RequestProcessorBase<TRequestContext, TRequestProcessorBase> where TRequestContext : RequestContextBase<TRequestProcessorBase> 

Das ist seltsam, und ich bin in der Regel nicht gern die curiously recurring template pattern. Außerdem erscheint mir die Vorstellung, dass der Benutzer einen Kontext füllt und mich darum bittet, eine Anfrage zu stellen, seltsam, obwohl das nur ein Problem mit der Namensgebung sein könnte.

3.) dachte ich über das Erhalten von allen oben genannten befreien und nur mit:

public AddRequest{ 
    public AddRequest(DependencyInjectionArg1, DependencyInjectionArg2, ...){} 

    public void PackArgs(UserSuppliedContextArg1, UserSuppliedContextArg2, UserSuppliedContextArg3, ...){} 
} 

die nicht schlecht ist, aber die API ist hässlich. Nun müssen die Clients dieses Objekts es gleichsam "konstruieren". Und wenn sie vergessen, PackArgs aufzurufen, muss ich eine Ausnahme auslösen.

Ich könnte weitermachen, aber das sind die verwirrendsten Probleme, die ich im Moment habe. Irgendwelche Ideen?

Antwort

0

Ayende hat eine few posts auf this subject.

Grundsätzlich, was Sie tun möchten, ist Ihre Abfrage von Ihrem Repository trennen, und verwandeln Sie Ihre Abfrage in etwas, das Sie bilden. Mit einer Abfrage, die durch Zusammensetzen erstellt wird, können Sie sie problemlos erweitern, um neue Abfrageverfahren hinzuzufügen, ohne dass Sie Ihrem Repository neue Methoden hinzufügen müssen. Sie können mit etwas am Ende wie folgt:

public class Repository<T> 
{ 
    T Find(IQueryCriteria queryCriteria); 
} 

Ich habe nicht wirklich noch diese in NHibernate getan, aber wir haben dies mit LLBLGenPro und es funktionierte wirklich gut.Wir haben eine fließend Schnittstelle für unsere Abfrage-Objekte so konnten wir Abfragekriterien wie folgt schreiben:

var query = new EmployeeQuery() 
    .WithLastName("Holmes") 
    .And() 
    .InDepartment("Information Systems"); 

var employee = repository.Find(query); 

Erweiterung der Fähigkeiten des Endlagers dann belief sich auf einfach neue Methoden für das Query-Objekt hinzufügen.

+0

Dies sieht etwas nützlich, aber meine Frage ist mehr über das Kapseln von Anfragen, nicht Abfragen. Außerdem greife ich nicht hinter dem Repository auf eine Datenbank zu, obwohl das wahrscheinlich keine Rolle spielt. – DavidN

1

Ein Repository ist Teil Ihrer Domain. Wenn es verführerisch wird, vereitelt es seinen Zweck als Heimat für Operationen in der allgegenwärtigen Sprache. Wenn Sie irgendetwas mit einem Repository tun können, haben Sie seine Absicht verschleiert.

Wenn eine Klasse SRP folgt, "ständiges Hinzufügen zu" verletzt dies definitionsgemäß. Dies weist darauf hin, dass die Vorgänge, die Sie einführen, möglicherweise besser durch Services angegangen werden oder anderweitig vom Repository entkoppelt werden.

bearbeiten als Antwort auf einen Kommentar

Sie möchten Ihre allgegenwärtige Sprache halten im Freien aus, während sichergestellt Klassen die Mindestmenge an Verantwortung.

Das Ausbrechen von Operationen aus dem Repository wäre der erste Schritt. Sie könnten so etwas tun:

public interface IOrderExpeditionService 
{ 
    void Expedite(IEnumerable<Order> orders); 
} 

public interface IOrderDataService 
{ 
    void GetOrderData(Order order, DateTime start, DateTime end); 
} 
+0

"Dies weist darauf hin, dass die Vorgänge, die Sie einführen, möglicherweise besser mit Diensten abgewickelt werden oder anderweitig vom Repository entkoppelt werden." - Das habe ich versucht zu tun. Haben Sie Vorschläge zum Entkoppelungsteil? – DavidN

+0

Große Antwort. Ich würde diese Dienste jedoch als Klassen implementieren. Es ist viel einfacher und immer noch einfach zu testen, sie und sogar die Klassen, die von ihnen abhängen. –

+0

Ich entschied mich, eine Schnittstelle zu verwenden, um eine Schnittstelle darzustellen, anstatt eine abstrakte Klasse zu verwenden, um dasselbe zu tun. Jedem sein eigenes. –

Verwandte Themen