5

Suppose I have the following class structure:

Class structure

Page

The Page, StaticPage and DynamicPage interfaces are to be implemented by the clients. They provide various data depending on the page type (static or dynamic), which is used to render the page by the Renderer. There might be many implementations of these interfaces.

Renderer

The Renderers render the pages. Also, there might be multiple implementations of this interface (for different rendering techniques).

RenderManager

This is just a simple facade which is supposed to invoke the appropriate render method on the provided renderer depending on the given page type. And here lies

The problem

How to determine which method to invoke on the Renderer object depending on the provided page type?

Current (unsatisfactory) solution

Currently I am dispatching using a conditional:

void render(Page page, Renderer renderer) {
    if (page is StaticPage) {
        renderer.renderStaticPage(page);
    } else if (page is DynamicPage) {
        renderer.renderDynamicPage(page);
    } else {
        throw new Exception("page type not supported");
    }
}

What's wrong

The problem with this solution is that whenever I wish to add another page type (i.e. extending the Page interface) I need to adjust this method too. Actually, this is what polymorphic (virtual) methods in object oriented languages are supposed be used for, but in this case this doesn't work (see below why).

Other solutions I considered but rejected

  1. Abstract class instead of interface. This would place an unneccessary constraint on the type hierarchy of the implementors: they would no longer be able to extend any class they want and would instead be forced to extend the abstract StaticPage or DynamicPage class, which sucks.

  2. Add a dispatch(Renderer render) method to the interface and force the implementors to call the appropriate method on the renderer object depending on the page type. This obviously sucks, because the implementors should not care about the rendering: they just need to provide data which is to be rendered.

So, maybe there is some pattern or some alternative design which might help in this situation? Any ideas are welcome. :)

proskor
  • 1,382
  • 9
  • 21
  • 1
    Two words: visitor pattern. – Patryk Ćwiek Sep 06 '13 at 10:03
  • Which would require adding an additional `accept` method to the `Page` interface, resulting in either solution 1 or 2 (both unsatisfactory) with implications I already discussed in my post. – proskor Sep 06 '13 at 10:05
  • Well, visitor pattern is a way to 'simulate' multiple dispatch in the languages that only support single dispatch... A more 'dirty' (?) trick is casting `page` to `dynamic`; then you can overload the renderer methods for multiple types (e.g. `Render(DynamicPage page)` and `Render(StaticPage page)`) and appropriate overload will be chosen at runtime. – Patryk Ćwiek Sep 06 '13 at 10:05
  • How is that different from what I am already doing? – proskor Sep 06 '13 at 10:09
  • 1
    @Proskor It wouldn't need you to go back and amend the code for new page types because you are duck-typing the existence of the method. If the method didn't exist, you'd get a run-time DLR binding exception. This, however, is not always the cleanest approach in terms of a defined contract. – Adam Houldsworth Sep 06 '13 at 10:10
  • 1
    @proskor - if your `Renderer` class had these two overloads, the body in snippet for `render` method in your post would look like this: `renderer.Render((dynamic)page);` and that's it. With runtime exception if there is no appropriate overload. – Patryk Ćwiek Sep 06 '13 at 10:11
  • 1
    @proskor Alternatively, have you thought about using attributes to define the type of renderer associated with an interface? Your rendering method would then just consist of reviewing the attribute to instantiate the renderer and pass the page in as an argument. The definition of the interface would then be responsible for defining what renderer is relevant and you could add new ones without amending the `RenderManager`. – Adam Houldsworth Sep 06 '13 at 10:13
  • According to [this question](http://stackoverflow.com/questions/479923/is-c-sharp-a-single-dispatch-or-multiple-dispatch-language), you might be able to do what you require with `dynamic`, however, I don't know if that would be the first indicator of a code-smell or not. I'm not sufficiently versed in either topics (`dynamic` or multiple-dispatch) to have an opinion on that. – Adam Houldsworth Sep 06 '13 at 10:22
  • @PatrykĆwiek This `(dynamic)` is *AWESOME*, exactly what I needed. Didn't know such feature exists. I tried it and worked like a charm. In my case, I don't think it's dirty because I am throwing an exception if the type is not supported anyway. Thank you very much guys! :) – proskor Sep 06 '13 at 10:27
  • @proskor Just be aware that `dynamic` uses the DLR and has the same access limitations that statically typed code does, in that access modifiers are honoured (you cannot duck-type to a private method for example). – Adam Houldsworth Sep 06 '13 at 10:29

1 Answers1

4

Use dynamic to select appropriate method overload at runtime:

public class RenderManager
{
    public void Render(IPage page, Renderer renderer)
    {
        try
        {
            renderer.RenderPage((dynamic)page);
        }
        catch (RuntimeBinderException ex)
        {
            throw new Exception("Page type not supported", ex);
        }
    }
}

But of course dynamic typing has performance costs. Benefits - when new type of page added, all you need to change is renderer - just add another overloaded method.


Another option is visitor. In this case each page should do dispatching (seems like your second approach):

public interface IPage
{
    void Render(Renderer renderer);
}

public class StaticPage : IStaticPage
{
    public void Render(Renderer renderer)
    {
        renderer.RenderPage(this);
    }
}

public class RenderManager
{
    public void Render(IPage page, Renderer renderer)
    {
        page.Render(renderer);
    }
}

In this case page 'knows' about rendering. And you still should modify renderer when adding new pages.

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459