Saturday, March 31, 2012

Purpose-Specific Visitors

This is a follow-up to my "Why visitor pattern is worth the overhead" entry. I'll be referring to the example in that entry...

One thing that's bothered me about the Visitor pattern is the syntax it introduces. In the example, our core domain is the concept of Feedback. On a Feedback object, you would expect to see methods/operations related to that problem domain.

However, if you implement the "textbook" Visitor pattern, you end up with:

// C# pseudocode...

abstract class Feedback
{
    void Accept(IFeedbackVisitor visitor);

    // Other domain meaningful methods
}

The 'accept' method is questionable. In the best case scenario, the word is meaningless to the domain, and we simply dismiss it as an annoying requirement of the pattern. In the worst case scenario, the word is meaningful to the domain but is a completely unrelated concept. So in this example, we could be misled to think that this method helps us accept or receive feedback from a user.

Let's express our true intention:

abstract class Feedback
{
    void DescribeTo(IFeedbackRenderer renderer);

    // Other domain meaningful methods
}

class StandardChoiceFeedback : Feedback
{
    int code;
    string description;
    void DescribeTo(IFeedbackRenderer renderer) { renderer.Render(description); }
}

class FreeFormFeedback : Feedback
{
    string text;
    void DescribeTo(IFeedbackRenderer renderer) { renderer.Render(text); }
}

class FollowUpCallFeedback : Feedback
{
    PhoneNumber phoneNumber;
    void DescribeTo(IFeedbackRenderer renderer) { renderer.Render(phoneNumber); }
}

// *******************************

interface IFeedbackRenderer
{
    void Render(String text);
    void Render(PhoneNumber phoneNumber);
}

Now, the purpose of the visitor is clear - we're getting a Feedback object to describe itself to another object that will help render it for display purposes.

I actually went one step further with this. Following "textbook" Visitor would have given us:

interface IFeedbackRenderer
{
    void Render(StandardChoiceFeedback feedback);
    void Render(FreeFormFeedback feedback);
    void Render(FollowUpCallFeedback feedback);
}

That's fine too. What I've done instead, is 'inline' the visitor methods to reflect what a Renderer would really need to do its rendering. An interesting effect with this approach is that the internals of each type of Feedback stay unexposed - no getters are required to make this work.

No comments: