2009-03-04 10 views
0

J'ai une classe à laquelle je suis constamment à ajouter. Il m'est apparu que cette classe n'est pas ouverte-fermée, à cause de l'ajout de toutes ces nouvelles fonctionnalités. J'ai donc pensé à fermer cette classe contre ce changement en encapsulant ces fonctions dans les objets Request. Je me retrouve avec quelque chose comme:Faire une classe suivre OCP - Fonctions Affacturage en objets

public abstract class RequestBase{} 

public class AddRequest : RequestBase{} 

etc... 

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

Ce qui rend OrderRepository ouvert pour l'extension et fermé pour modification. Cependant, je suis tombé rapidement en quelques problèmes avec cela:

1.) Les données de la demande qui doit fonctionner sur est à la fois fourni par l'utilisateur (exécution) et l'injection fourni par la dépendance. Je ne peux évidemment pas satisfaire les deux avec un constructeur. Je ne peux pas faire:

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

et appeler cela. Je voudrais un moyen pour le cadre DI de construire "partiellement" un objet pour moi, et laissez-moi faire le reste. Je ne vois aucun moyen de le faire cependant. J'ai vu un blog qui appelait ce concept "injection de constructeur variable".

2.) La prochaine chose que je pensais était de diviser cela en 2 classes séparées. L'utilisateur de créer et remplir un RequestContext, puis passer que dans le référentiel, ce qui créerait un RequestProcessor (ne peut pas penser à un meilleur nom) hors de celui-ci. J'ai pensé à faire:

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>(); 
    } 
} 

et c'était un bon premier pas. Cependant, le processeur de requêtes a besoin du type exact de contexte qu'il stocke, ce que je n'ai pas ici. Je pourrais utiliser un dictionnaire de types de types, mais les défaites dans le but d'être ouvert-fermé .Donc je finis par avoir à faire quelque chose comme:

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

C'est bizarre et je suis généralement pas friands de la curiously recurring template pattern. De plus, l'idée que l'utilisateur remplisse un contexte et me demande d'en faire la demande me semble étrange, bien que cela puisse être juste un problème de dénomination.

3.) Je pensais à se débarrasser de tous les ci-dessus et juste avoir:

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

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

qui est pas mal, mais l'API est laid. Maintenant, les clients de cet objet doivent le «construire» deux fois, pour ainsi dire. Et s'ils oublient d'appeler PackArgs, je dois lancer une exception quelconque.

Je pourrais continuer, mais ce sont les questions les plus confuses que j'ai en ce moment. Des idées?

Répondre

0

Ayende a un few posts sur this subject.

Fondamentalement, ce que vous voulez faire est de détacher votre requête de votre dépôt, et transformer votre requête en quelque chose que vous composer. Avec une requête construite par composition, vous pouvez l'étendre facilement pour ajouter de nouvelles façons d'interroger sans avoir à ajouter de nouvelles méthodes à votre Repository. Vous pouvez retrouver avec quelque chose comme ceci:

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

Je ne l'ai pas vraiment fait cela dans NHibernate, mais nous l'avons fait avec LLBLGenPro et ça a vraiment bien.Nous avons utilisé une interface fluide pour notre requête objets afin que nous puissions écrire des critères de requête comme ceci:

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

var employee = repository.Find(query); 

élargir les capacités du dépôt puis simplement se sont élevées en ajoutant de nouvelles méthodes sur l'objet de la requête.

+0

Cela semble plutôt utile, mais ma question concerne davantage l'encapsulation de requêtes, pas de requêtes. En outre, je n'accède pas à une base de données derrière le référentiel, même si cela ne devrait probablement pas avoir d'importance. – DavidN

1

Un référentiel fait partie de votre domaine. Le rendre générique, tout en tentant, détruit son but en tant que foyer d'opérations dans le langage omniprésent. Si vous pouvez faire n'importe quoi avec un dépôt, vous avez obscurci son intention.

Si une classe suit SRP, "constamment ajouter à" viole cela par définition. Cela indique que les opérations que vous introduisez peuvent être mieux traitées par les services ou devraient être découplées du référentiel.

Modifier en réponse au commentaire

Vous voulez garder votre langue omniprésente à l'air libre, alors que les classes assurant ont le montant minimum de la responsabilité.

La suppression des opérations du référentiel constituerait la première étape. Vous pourriez faire quelque chose comme ceci:

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

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

"Cela indique que les opérations que vous introduisez peuvent être mieux traitées par les services ou devraient être découplées du référentiel." - C'est ce que j'ai essayé de faire. Avez-vous des suggestions sur la partie découplage? – DavidN

+0

Bonne réponse. Je voudrais implémenter ces services en tant que classes, cependant. C'est beaucoup plus simple, et encore facile à tester unitaire et même les classes qui en dépendent. –

+0

J'ai choisi d'utiliser une interface pour représenter une interface, plutôt que d'utiliser une classe abstraite pour faire la même chose. À chacun ses goûts. –