Является ли это типичным прецедентом для МОК?
Мое текущее приложение позволяет пользователям определять пользовательские веб-формы с помощью набора экранов администратора. это, по сути, приложение типа EAV. Таким образом, я не могу написать код HTML или ASP.NET для рендеринга данной страницы. Вместо этого пользовательский интерфейс запрашивает экземпляр объекта Form из служебного уровня, который, в свою очередь, строит один, используя несколько таблиц RDMBS. Форма содержит типы классов, которые вы ожидаете увидеть в таком контексте: Form
= > IEnumerable<FormSections>
= > IEnumerable<FormFields>
Вот как выглядит сервисный слой:
public class MyFormService: IFormService{
public Form OpenForm(int formId){
//construct and return a concrete implementation of Form
}
}
Все работает великолепно (на некоторое время). Пользовательский интерфейс не является более разумным в отношении того, какие разделы/поля существуют в данной форме: он с радостью отображает объект Form, который он получает, на функциональную страницу ASP.NET.
Несколько недель спустя я получаю новое требование от бизнеса: при просмотре не редактируемых (т.е. только для чтения) версий формы некоторые значения полей должны быть объединены вместе, а другие надуманные/рассчитанные поля должны быть добавлены, Без проблем я говорю. Просто измените мой класс обслуживания, чтобы его методы были более явными:
public class MyFormService: IFormService{
public Form OpenFormForEditing(int formId){
//construct and return a concrete implementation of Form
}
public Form OpenFormForViewing(int formId){
//construct and a concrete implementation of Form
//apply additional transformations to the form
}
}
Снова все работает отлично, и равновесие восстановлено силой. Пользовательский интерфейс продолжает быть агностическим в отношении того, что находится в Форме, и наше разделение проблем достигнуто. Однако всего несколько коротких недель спустя бизнес выдвигает новое требование: в некоторых сценариях мы должны применять только некоторые из преобразований формы, на которые я ссылался выше.
На данный момент похоже, что подход "явного метода" достиг тупиковой ситуации, если я не хочу в конечном итоге вызвать взрыв методов (OpenFormViewingScenario1, OpenFormViewingScenario2 и т.д.). Вместо этого я вводил еще один уровень косвенности:
public interface IFormViewCreator{
void CreateView(Form form);
}
public class MyFormService: IFormService{
public Form OpenFormForEditing(int formId){
//construct and return a concrete implementation of Form
}
public Form OpenFormForViewing(int formId, IFormViewCreator formViewCreator){
//construct a concrete implementation of Form
//apply transformations to the dynamic field list
return formViewCreator.CreateView(form);
}
}
На первый взгляд это похоже на приемлемый подход, и все же есть определенный запах. А именно, пользовательский интерфейс, который жил в невежественном блаженстве о деталях реализации OpenFormForViewing, должен обладать знаниями и создавать экземпляр IFormViewCreator.
- Мои вопросы двоякие: есть ли
лучший способ достичь Я могу последовать за композицией? (возможно, использование контейнера IoC или дома показывается factory, чтобы создать конкретный IFormViewCreator)?
- Я принципиально испортил абстракция здесь?
Ответы
Ответ 1
Как я понимаю, вам нужно изменить форму перед отправкой ее на уровень пользовательского интерфейса. Это звучит для меня как Decorator. Храните старый интерфейс IFormService без IFormViewCreator.
Теперь вы можете создать один или несколько отделочных форм FormService, которые реализуют требуемую фильтрацию или модификацию.
public class MyDecoratingFormService : IFormService
{
private readonly IFormService formService;
public MyDecoratingFormService(IFormService formService)
{
if(formService == null)
{
throw new ArgumentNullException("formService");
}
this.formService = formService;
}
public Form OpenFormForEditing(int formId)
{
var form = this.formService.OpenFormForEditing(formId);
return this.TransformForm(form);
}
public Form OpenFormForViewing(int formId)
{
var form = this.formService.OpenFormForViewing(formId);
return this.TransformForm(form);
}
public Form TransformForm(Form form)
{
// Implement transformation/filtering/modification here
}
}
Теперь вы можете украсить свою оригинальную реализацию IFormService одним или несколькими такими Декораторами.
IFormService formService = new MyDecoratingFormService(new MyFormService());
Вы можете обернуть столько декораторов (каждый со своей ответственностью) вокруг друг друга, сколько захотите.
Здесь нет явной необходимости в контейнере DI, но он хорошо подходит для других шаблонов DI. Я использую Decorator все время:)
Ответ 2
Если я не хочу закончить взрыв методов (OpenFormViewingScenario1, OpenFormViewingScenario2 и т.д.).
Вы не можете удалить сложность требований. Так или иначе, вам нужно будет определить прецедент и действовать соответствующим образом.
Вы можете перечислить все возможные комбинации или попытаться разложить обработку разумно - если это возможно. Например, если из-за самой природы требования существуют возможности N * M = X
, то наличие исчерпывающего списка методов X
действительно не является хорошим. Вы должны учитывать так, чтобы создать X
возможные формы из состава случаев N
и M
.
Не зная точного требования, это трудно сказать. Тогда существует много возможных способов определения такой композиции, например. Decorator, ChainOfResponsability, Filter и т.д. Это все способы составить сложную логику.
А именно, пользовательский интерфейс, который жил в невежественном блаженстве о детали реализации OpenFormForViewing, должен обладать знание и создание экземпляра IFormViewCreator.
Презентация должна быть предоставлена форма, созданная где-то в другом месте, чтобы оставаться агностиком от создания формы. Однако форма должна быть собрана/создана где-то.
В MVC такая логика входит в контроллер, который обновляет модель/форму и отправляет на правильный просмотр/рендеринг. То же самое можно сделать с помощью ASP.NET