Question

I am currently developing two forms which are pretty similar in functionality, although one form is to be used on a PC and the other form is tailored to be used on a low-resolution scanner device.

I want to share as much logic as possible, that's why I am using MVP (Passive View) for this.

But the scanner version has some slight differences to it, for example to dynamically show and hide some controls. This is of course somthing I want to put in my Presenter as well so I can unit test it.

So my question is: should I put this logic in the same presenter? Or should I make a variation of this presenter by inheriting from it? Or should I simply make everything separate, dedicated to the specific form, although my View and Model are exactly the same for both forms?

No correct solution

OTHER TIPS

So my question is: should I put this logic in the same presenter?

In my opinion, no. The presenters have different responsibilities so the logic should be separated into dedicated presenter classes.

should I make a variation of this presenter by inheriting from it?

In my opinion, inheritance is the wrong solution to this problem. The only thing that the presenters have in common is that they happen to act upon the same view and model pair.

So you need to move the presenter functionality into separate classes but without using inheritance. Earlier I stated that the only commonality between the presenters is that they act upon the same view and model pair. With this in mind we can create the following abstraction for a presenter:

public interface Presenter<M, V>
{
    M Model { get; }
    V View { get; }
}

You can implement this interface to create a concrete presenter which provides the desired view and model pair for presenter subclasses to act upon. Next you create separate presenter classes for your PC and Scanner presenters which provide the features necessary for each view.

The idea is you can now use the decorator pattern to build up the desired presenter functionality depending on the current view. To facilitate this you can create the following class:

public abstract class PresenterDecorator<M, V> : Presenter<M, V>
{
    private readonly Presenter<M, V> decoratedPresenter;

    public PresenterDecorator(Presenter<M, V> decoratee)
    {
        this.decoratedPresenter = decoratee;
    }

    public M Model
    {
        get { return this.decoratedPresenter.Model; }
    }


    public V View
    {
        get { return this.decoratedPresenter.View; }
    }

}

This allows you to wrap existing presenters and augment them with additional functionality providing they act on the same view and model pair.

To put all this to use you will first require a concrete presenter that provides the relevant view and model types. Ideally the view and model will be injected into the constructor meaning this presenter should do nothing else but return the view and model from the properties provided by the implementation of the Presenter interface. Ideally, the model should be injected with it's default values already set. Note that this functionality can be moved into a generic base class which allows you to initialize any view / model pair.

Next, for each piece of discrete functionality for a view / model pair you must create a presenter which derives from the PresenterDecorator class. In your case you would have DefaultPresenter and ScannerPresenter. Each of these presenter classes can also provide custom constructors which also provide any required services in addition to the presenter to be decorated.

In the part of your application that is responsible for opening the PC screen and configuring your presenters you would do something like this:

var model = ... 
var view = ...      

var presenter = new DefaultPresenter(new PresenterInitializer(view, model));

and in the part of your application that is responsible for opening the scanner screen and configuring your presenters you would do something like this:

var model = ... 
var view = ...      

var presenter = 
    new ScannerPresenter(new DefaultPresenter(PresenterInitializer(view, model)));

Notice that when you configure the scanner screen, the default presenter is decorated with the ScannerPresenter class which provides the additional features you require (for maximum flexibility each presenter decorator should be written so that construction order does not matter).

A dependency injection framework can be used to simplify the wiring and configuration of the application so that the above code does not need to be re-written by hand if requirements change.

Now this looks like a lot of work to get up and running but once the core abstractions are in place it really makes it easy to add new features to your application.

If a new requirement is given, you simply create a new presenter class which derives from the PresenterDecorator class and change your configuration code to accommodate (hint: using a dependency injection framework to make this easier). Notice that you don't have to touch any of the existing presenter classes. Each presenter class can also be tested in complete isolation.

You can also create generic presenter decorators which can be applied to any view and model pair which make thing like data validation very straightforward.

I understand this is a long answer and there is a lot to take in so please leave a comment if you require any further clarification or have any thoughts about using this approach.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top