Заставить класс следовать OCP - Факторинг функций в объекты - PullRequest
0 голосов
/ 04 марта 2009

У меня есть класс, к которому я постоянно добавляю.

public class OrderRepository{
    public void Add(IEnumerable<Order> orders){}
    public void Update(IEnumerable<Order> orders){}
    public void Remove(IEnumerable<Order> orders){}
    public void Expedite(IEnumerable<Order> orders){}
    public void GetOrderData(Order order, DateTime start, DateTime end)
    etc...
}

Мне пришло в голову, что этот класс не является открытым-закрытым из-за добавления всех этих новых функций. Поэтому я подумал о том, чтобы закрыть этот класс от этого изменения, заключив эти функции в объекты Request. Я получаю что-то вроде:

public abstract class RequestBase{}

public class AddRequest : RequestBase{}

etc...

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

Что делает OrderRepository открытым для расширения и закрытым для модификации. Тем не менее, я быстро столкнулся с несколькими проблемами с этим:

1.) Данные, с которыми должен работать этот запрос, предоставляются как пользователем (во время выполнения), так и введением зависимости. Я, очевидно, не могу удовлетворить оба с одним конструктором. Я не могу сделать:

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

и назовите это. Я хотел бы, чтобы структура DI «частично» создала объект для меня, и позвольте мне сделать все остальное. Я не вижу никакого способа сделать это как бы то ни было. Я видел блог, который называл эту концепцию «инъекцией в конструктор переменных».

2.) Следующее, о чем я подумал, это разделить это на 2 отдельных класса. Пользователь должен создать и заполнить RequestContext, а затем передать его в репозиторий, который создаст из него RequestProcessor (не может придумать лучшего имени). Я думал о том, чтобы сделать:

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

и это был хороший первый шаг. Однако обработчику запросов необходим точный тип сохраняемого контекста, которого у меня нет. Я мог бы использовать словарь типов для типов, но это противоречит цели быть открытым-закрытым. Так что в итоге я должен был сделать что-то вроде:

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

Это странно, и мне обычно не нравится любопытно повторяющийся шаблон . Кроме того, идея о том, что пользователь заполняет контекст и просит меня сделать запрос, кажется странной, хотя это может быть просто проблемой именования.

3.) Я думал о том, чтобы избавиться от всего вышеперечисленного и просто иметь:

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

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

что неплохо, но API уродлив. Теперь клиентам этого объекта нужно как бы «построить» его дважды. И если они забывают вызвать PackArgs, я должен вызвать какое-то исключение.

Я мог бы продолжить, но это самые запутанные проблемы, которые у меня есть на данный момент. Есть идеи?

Ответы [ 2 ]

1 голос
/ 04 марта 2009

Репозиторий является частью вашего домена. Делая его универсальным, хотя и заманчивым, наносит ущерб его цели как дома для операций на вездесущем языке. Если вы можете сделать что-нибудь с хранилищем, вы запутали его намерения.

Если класс следует SRP, «постоянно добавляя к», он нарушает это по определению. Это указывает на то, что вводимые вами операции могут лучше решаться службами или иным образом должны быть отделены от репозитория.

Редактировать в ответ на комментарий

Вы хотите, чтобы ваш вездесущий язык оставался открытым, при этом гарантируя, что классы несут минимальную ответственность.

Выделение операций из хранилища будет первым шагом. Вы можете сделать что-то вроде этого:

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

public interface IOrderDataService
{
    void GetOrderData(Order order, DateTime start, DateTime end);
}
0 голосов
/ 04 марта 2009

Айенде имеет несколько сообщений на эту тему .

По сути, вы хотите отсоединить ваш запрос от вашего хранилища и превратить ваш запрос в то, что вы составляете . С помощью составленного запроса вы можете легко расширить его, добавив новые способы запроса, не добавляя новые методы в свой репозиторий. Вы можете получить что-то вроде этого:

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

На самом деле я еще не делал этого в NHibernate, но мы сделали это с LLBLGenPro, и это сработало очень хорошо. Мы использовали свободный интерфейс для наших объектов запросов, чтобы мы могли написать критерии запроса, такие как:

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

var employee = repository.Find(query);

Расширение возможностей хранилища сводится к простому добавлению новых методов в объект запроса.

...