tags:

views:

209

answers:

9

Hi all, I have this doubt for a long time... hope anyone can enlight me.

Suppose I have 3 classes in my model.

abstract class Document {}
class Letter extends Document {}
class Email extends Document {}

and a service class with a method that returns a Document (either a Letter or Email).

class MyService {
    public Document getDoc(){...}
}

So in my controller I want to display the Document returned by MyService, and I want it to be displayed using a view for the Email and other for the Letter. How could a controller know which document view invoke? the letterView or the emailView?.

Often I make an if statement on the controller to check the type of the Document received by the service tier... however I dont think it is the best approach from the OOP point of view, also if I implement a few boolean methods Document.isLetter(), Document.isEmail() the solution is, in essence, the same.

Another thing is to delegate the view selection to the Document somehow. something like:

class MyController {
    public View handleSomething() {
        Document document = myService.getDocument();
        return document.getView();
    }
}

But, omg, why my model objects must know anything about the view?

Any toughts are appreciated :)

+1  A: 

Maybe you could have something like getView() in the Document, overriding it in each implementation?

Tronic
hi! apologies for -1, but this is not well-advised. a "Model" represents business data [sometimes we get lazy and add business logic too :S ]. however, the Model does not know how it should be presented. consider an "end-user" app vs an "admin" app. both applications may leverage the same business tier, and Model, but each may wish a different View [admin may have more data]. embedding View selection in Model would constrain the same view to both applications. unless you implement an override, rendering the method moot. generally speaking, Model should be presentation-agnostic.
johnny g
+2  A: 

Your controller shouldn't know. It should ask the Document to display itself, and the Document can do this, or provide sufficient information to let the View handle this polymorphically.

Imagine if at a later stage you add a new Document type (say, Spreadsheet). You really only want to add the Spreadsheet object (inheriting from Document) and have everything work. Consequently the Spreadsheet needs to provide the capability to display itself.

Perhaps it can do it standalone. e.g.

new Spreadsheet().display();

Perhaps it can do it in conjunction with the View e.g. a double-dispatch mechanism

new Spreadsheet().display(view);

In either case, the Spreadsheet/Letter/Email would all implement this view() method and be responsible for the display. Your objects should talk in some view-agnostic language. e.g. your document says "display this in bold". Your view can then interpret it according to its type. Should your object know about the view ? Perhaps it needs to know capabilities that that view has, but it should be able to talk in this agnostic fashion without knowing the view details.

Brian Agnew
@Brian Agnew - I like this answer but I think it keeps people puzzled as to how to accomplish this. Even though you state `new spreadsheet().display();` I guarantee people question what display looks like...will people end up with code testing typeof(eachObject). If you go a bit more in depth about display I will +1 you. :)
JonH
A: 

Extend your service to return the type of the document:

class MyService {

    public static final int TYPE_EMAIL = 1;
    public static final int TYPE_LETTER = 2;

    public Document getDoc(){...}
    public int getType(){...}
}

In a more object oriented approach, use a ViewFactory return a different view for e-mails and letters. Use view handlers with a ViewFactory and you can ask each of the handlers if it can handle the document:

class ViewFactory {
    private List<ViewHandler> viewHandlers;

    public viewFactory() {
       viewHandlers = new List<ViewHandler>();
    }

    public void registerViewHandler(ViewHandler vh){
       viewHandlers.add(vh);
    }

    public View getView(Document doc){
        for(ViewHandler vh : viewHandlers){
           View v = vh.getView(doc);
           if(v != null){
             return v;
           }
        }
        return null;
    }
}

With this factory, your factory class doesn't need to change when you add new view types. The view types can each check if they can handle the document type given. If they can't they can return null. Otherwise, they can return the view you need. If no view can handle your document, null is returned.

The ViewHandlers can be really simple:

public interface ViewHandler {
   public getView(Document doc)
}

public class EmailViewHandler implements ViewHandler {
   public View getView(Document doc){
       if(doc instanceof Email){
         // return a view for the e-mail type
       } 
       return null;  // this handler cannot handle this type
   }
}
Scharrels
The question is about good object-oriented programming practice. Your answer uses a procedural approach, not an object-oriented approach. The problem is that the procedural approach does not scale well because the "client code" is strongly coupled to the current implementation of the "library code".
richj
I realized that. I hope that my current answer suits the question better.
Scharrels
Sure - but if you need type tests I would be inclined to put them in the factory class. You can probably do better than this by registering the handler against the view class.
richj
+2  A: 

I am not sure, but you can try add a factory class that based on functions overriding, and assumed to return a view depending on Document type. For example:

class ViewFactory {
    public View getView(Letter doc) {
         return new LetterView();
    }
    public View getView(Email doc) {
         return new EmailView();
    }
}
woo
The "Factory" pattern is the best way I think. It should be in the "common" package, where your interfaces are too. +1
Clement Herreman
I don't think that will work. In order to call the ViewFactory.getView() method you need a reference of the proper type (Letter or Email) but the service returns a Document, leaving us with the original problem. Thanx!
Mauricio
I thought that service return an object of specific type, that was casted down to document. Am I wrong?
woo
yep, the service returns a Document
Mauricio
yes, according to signature this is a Document. And I think, it castdown a proper type like an Email or Letter.
woo
yes but, at least in java, the parameter matching of a method call is done at compile time. So, with a Document reference, you cant call getView(Letter d) nor getView(Email d)
Mauricio
@Mauricio: +1. You are correct; I just tested this.
Nelson
+11  A: 

This is a great question. There is more than one plausible approach here; you have to balance the trade-offs and make a choice that suits your circumstances.

(1) Some will argue that that Document interface should provide a method for instances to render themselves. This is attractive from an OO standpoint, but depending on your view technologies, it may be impractical or downright ugly to load your concrete Document classes -- which are probably simple domain model classes -- with knowledge of JSPs, Swing Components, or whatever.

(2) Some will suggest putting perhaps a String getViewName() method on Document that returns, for example, the path to a JSP file that can properly render that document type. This avoids the ugliness of #1 at one level (library dependencies/"heavy lifting" code), but conceptually poses the same problem: your domain model knows that it's being rendered by JSPs and it knows the structure of your webapp.

(3) Despite these points, it's better if your Controller class doesn't know what types of documents exist in the universe and which type each instance of Document belongs to. Consider setting up some sort of view-mapping in some sort of text-based file: either .properties or .xml. Do you use Spring? Spring DI can help you quickly specify a Map of concrete Document classes and the JSP/view components that render them, then pass it to a setter/constructor of you Controller class. This approach allows both: (1) your Controller code to remain agnostic of Document types and (2) your domain model to remain simple and agnostic of view technologies. It comes at the cost of incremental configuration: either .properties or .xml.

I'd go for #3 or -- if my budget (in time) for working on this problem is small -- I'd (4) simply hard-code some basic knowledge of Document types in my Controler (as you say you're doing now) with a view toward switching to #3 in the future the next time I'm forced to update my Controller due to less-than-optimal OO characteristics. The fact is that #s 1-3 each take longer and are more complex than #4, even if they are "more correct." Sticking with #4 is also a nod to the YAGNI Principal: there's no certainty you'll ever experience the negative effects of #4, does it make sense to pay the cost of avoiding them up-front?

Drew Wills
I'd vote this up twice if I could. Very good answer.
JacobM
+1  A: 

Hi, I have seen this "pattern" many times in my work, and have seen many approaches to solve it. To the point, I would suggest

  1. Create a new service IViewSelector

  2. Implement IViewSelector, either by hardcoding mappings or by configuration, and throwing NotSupportedException whenever an invalid request is made.

This performs the mapping you require while facilitating Separation of Concern [SoC]

// a service that provides explicit view-model mapping
// 
// NOTE: SORRY did not notice originally stated in java,
// pattern still applies, just remove generic parameters, 
// and add signature parameters of Type
public interface IViewSelector
{

    // simple mapping function, specify source model and 
    // desired view interface, it will return an implementation
    // for your requirements
    IView Resolve<IView>(object model);

    // offers fine level of granularity, now you can support
    // views based on source model and calling controller, 
    // essentially contextual views
    IView Resolve<IView, TController>(object model);

}

As an example of usage, consider the following

public abstract Document { }
public class Letter : Document { }
public class Email : Document { }

// defines contract between Controller and View. should
// contain methods common to both email and letter views
public interface IDocumentView { }
public class EmailView : IDocumentView { }
public class LetterView : IDocumentView { }

// controller for a particular flow in your business
public class Controller 
{
    // selector service injected
    public Controller (IViewSelector selector) { }

    // method to display a model
    public void DisplayModel (Document document)
    {
        // get a view based on model and view contract
        IDocumentView view = selector.Resolve<IDocumentView> (model);
        // er ... display? or operate on?
    }
}

// simple implementation of IViewSelector. could also delegate
// to an object factory [preferably a configurable IoC container!]
// but here we hard code our mapping.
public class Selector : IViewSelector
{
    public IView Resolve<IView>(object model)
    {
        return Resolve<IView> (model, null);
    }

    public IView Resolve<IView, TController>(object model)
    {
        return Resolve<IView> (model, typeof (TController));
    }

    public IView Resolve<IView> (object model, Type controllerType)
    {
        IVew view = default (IView);
        Type modelType = model.GetType ();
        if (modelType == typeof (EmailDocument))
        {
            // in this trivial sample, we ignore controllerType,
            // however, in practice, we would probe map, or do
            // something that is business-appropriate
            view = (IView)(new EmailView(model));
        }
        else if (modelType == typeof (LetterDocument))
        {
            // who knows how to instantiate view? well, we are
            // *supposed* to. though named "selector" we are also
            // a factory [could also be factored out]. notice here
            // LetterView does not require model on instantiation
            view = (IView)(new LetterView());
        }
        else 
        {
            throw new NotSupportedOperation (
                string.Format (
                "Does not currently support views for model [{0}].", 
                modelType));
        }
        return view;
    }
}
johnny g
+1 for the theory but the implementation can be significantly improved. Using generics you should be able to eliminate the references to objects and explicit type checking/casting.
CurtainDog
+1  A: 

The Visitor pattern might work here:

abstract class Document {
    public abstract void accept(View view);
}

class Letter extends Document {
    public void accept(View view) { view.display(this); }
}

class Email extends Document {
    public void accept(View view) { view.display(this); }
}

abstract class View {
    public abstract void display(Email document);
    public abstract void display(Letter document);
}

Visitor is one of the more controversial patterns, although there are a number of variants that try to overcome the original pattern's limitations.

It would be easier to implement if the accept(...) method could be implemented in Document, but the pattern relies on the static type of the "this" parameter, so I don't think this is possible in Java - you have to repeat yourself in this case because the static type of "this" depends on the class holding the implementation.

If the number of document types is relatively small and unlikely to grow, and the number of view types is more likely to grow, then this would work. Otherwise I would look for an approach that uses a third class to coordinate the display and try to keep View and Document independent. This second approach might look like this:

abstract class Document {}
class Letter extends Document {}
class Email extends Document {}

abstract class View {}
class LetterView extends View {}
class EmailView extends View {}

class ViewManager {
    public void display(Document document) {
        View view = getAssociatedView(document);
        view.display();
    }

    protected View getAssociatedView(Document document) { ... }
}

The purpose of the ViewManager is to associate document instances (or document types if only one document of a given type can be open) with view instances (or view types if only one view of a given type can be open). If a document can have multiple associated views then the implementation of ViewManager would look like this instead:

class ViewManager {
    public void display(Document document) {
        List<View> views = getAssociatedViews(document);

        for (View view : views) {
            view.display();
        }
    }

    protected List<View> getAssociatedViews(Document document) { ... }
}

The view-document association logic depends on your application. It can be as simple or as complicated as it needs to be. The association logic is encapsulated in the ViewManager so it should be relatively easy to change. I like the points that Drew Wills made in his answer regarding dependency injection and configuration.

richj
+1  A: 

First off, Drew Wills' response is absolutely great -- I'm new here and I don't have the reputation to vote on it yet, or else I would.

Unfortunately, and this may be my own lack of experience, I don't see any way that you're going to avoid compromising some separation of concern. Something is going to have to know what kind of View to create for a given Document -- there's just no way around that.

As Drew pointed out in point #3, you could go with some kind of external configuration that would instruct your system on which View class to use for which document type. Drew's point #4 is also a decent way to go, because even though it breaks the Open/Closed principle (I believe that's the one I'm thinking of), if you're only going to have a handful of Document sub types, it probably isn't really worth fussing with.

For a variation on that latter point, if you want to avoid using type checks, you could implement a factory class/method that relies on a Map of Document sub types to View instances:

public final class DocumentViewFactory {
    private final Map<Class<?>, View> viewMap = new HashMap<Class<?>, View>();

    private void addView(final Class<?> docClass, final View docView) {
        this.viewMap.put(docClass, docView);
    }

    private void initializeViews() {
        this.addView(Email.class, new EmailView());
        this.addView(Letter.class, new LetterView());
    }

    public View getView(Document doc) {
        if (this.viewMap.containsKey(doc.getClass()) {
            return this.viewMap.get(doc.getClass());
        }

        return null;
    }
}

Of course, you'll still need to edit the initializeViews method whenever you need to add a new view to the map -- so it still violates OCP -- but at least your changes are going to be centralized to your Factory class instead of inside your controller.

(I'm sure there's lots that could be tweaked in that example -- validation, for one -- but it should be good enough to get a good idea of what I'm getting at.)

Hope this helps.

MCory
+1  A: 

Just do it!

public class DocumentController {
   public View handleSomething(request, response) {
        request.setAttribute("document", repository.getById(Integer.valueOf(request.getParameter("id"))));

        return new View("document");
    }
}

...

// document.jsp

<c:import url="render-${document.class.simpleName}.jsp"/>

Nothing else!

Arthur Ronald F D Garcia
@Mauricio As shown above, It can output either render-Email.jsp or render-Letter.jsp
Arthur Ronald F D Garcia