views:

330

answers:

2

My current application allows users to define custom web forms through a set of admin screens. it's essentially an EAV type application. As such, I can't hard code HTML or ASP.NET markup to render a given page. Instead, the UI requests an instance of a Form object from the service layer, which in turn constructs one using a several RDMBS tables. Form contains the kind of classes you would expect to see in such a context: Form=> IEnumerable<FormSections>=>IEnumerable<FormFields>

Here's what the service layer looks like:

public class MyFormService: IFormService{

       public Form OpenForm(int formId){
          //construct and return a concrete implementation of Form 
       }
}

Everything works splendidly (for a while). The UI is none the wiser about what sections/fields exist in a given form: It happily renders the Form object it receives into a functional ASP.NET page.

A few weeks later, I get a new requirement from the business: When viewing a non-editable (i.e. read-only) versions of a form, certain field values should be merged together and other contrived/calculated fields should are added. No problem I say. Simply amend my service class so that its methods are more explicit:

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
       }
}

Again everything works great and balance has been restored to the force. The UI continues to be agnostic as to what is in the Form, and our separation of concerns is achieved. Only a few short weeks later, however, the business puts out a new requirement: in certain scenarios, we should apply only some of the form transformations I referenced above.

At this point, it feels like the "explicit method" approach has reached a dead end, unless I want to end up with an explosion of methods (OpenFormViewingScenario1, OpenFormViewingScenario2, etc). Instead, I introduce another level of indirection:

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

On the surface, this seems like acceptable approach and yet there is a certain smell. Namely, the UI, which had been living in ignorant bliss about the implementation details of OpenFormForViewing, must possess knowledge of and create an instance of IFormViewCreator.

  1. My questions are twofold: Is there a better way to achieve the composability I'm after? (perhaps by using an IoC container or a home rolled factory to create the concrete IFormViewCreator)?
  2. Did I fundamentally screw up the abstraction here?
+7  A: 

As I understand the question, you need to modify the Form before sending it off to the UI layer. That sounds to me like a Decorator would be in place. Keep the old IFormService interface without the IFormViewCreator.

You can now create one or more Decorating FormService(s) that implement the desired filtering or modification.

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
    }
}

You can now decorate your original IFormService implementation with one or more of such Decorators.

IFormService formService = new MyDecoratingFormService(new MyFormService());

You can wrap as many Decorators (each with their own responsibility) around each other as you'd like.

There's no explicit need for a DI Container to do this, but it fits nicely with other DI patterns. I use Decorator all the time :)

Mark Seemann
+1 for a good response. I'm hoping to generate more discussion before I award an answer.
Dirk
@Dirk - if you think it is a good response, why not accept the answer???
Christian Payne
Because SO won't let me. There is no "accept" check mark available. Is that because I put a bounty on this question?
Dirk
Yes, you should have accepted the answer while the bounty was on. If, however, you still want to accept it, you might ask for help on this on the meta forum http://meta.stackoverflow.com/ . I sure wouldn't mind :)
Mark Seemann
@Dirk @Mark Once you've had a (failed) bounty that just isn't possible.
Marc Gravell
A: 

Unless I want to end up with an explosion of methods (OpenFormViewingScenario1, OpenFormViewingScenario2, etc).

You can not remove the inherent complexity of the requirements. One way or another, you will need to detect the use case and act accordingly.

You can list all possible combinations or try to factor out the processing intelligently -- if it's possible. For instance, if due to the very nature of the requirement there are N * M = X possibilities, then having an exhaustive listing of X methods is indeed not good. You should factor so has to create the X possible forms from a composition of the N and M cases.

Without knowing the exact requirement it's hard to say. Then there are many possible way to factor such a composition, e.g. Decorator, ChainOfResponsability, Filter, etc. These are all ways to compose complex logic.

Namely, the UI, which had been living in ignorant bliss about the implementation details of OpenFormForViewing, must possess knowledge of and create an instance of IFormViewCreator.

The presentation should be handed out a form created somewhere else, so as to remain agnostic from the form creation. The form will however need to be assembled/created somewhere.

In MVC, such logic goes in the controller which would update the model/form and dispatch to the correct view/rendering. The same can be done with ASP.NET

ewernli