2

On the net and in books you commonly see examples of the visitor pattern just using "commands" i.e. methods perform an action and have no return type.

Is it OK to use this pattern for a query function as well? Take for example this Visitor interface which gets the status description from the specified Status object...

public interface IVisitor
{
    string GetDefaultStatusDescription(DefaultStatus status);
    string GetNewStatusDescription(NewStatus status);
    string GetAciveStatusDescription(AciveStatus status);
    string GetDeactivatedStatusDescription(DeactivatedStatus status);
}

UPDATED IVisitor based upon @JacquesB comment.

public interface IVisitor
{
    string GetStatusDescription(DefaultStatus status);
    string GetStatusDescription(NewStatus status);
    string GetStatusDescription(AciveStatus status);
    string GetStatusDescription(DeactivatedStatus status);
}
  • 3
    It is OK if it solves your problem. If it doesn't solve your problem, it is not OK. Patterns are tools, not rituals. In any case, that interfaces is not a visitor since there is no overloading involved. – JacquesB Feb 21 '17 at 14:55
  • Should it be.. ` public interface IVisitor { string GetStatusDescription(DefaultStatus status); string GetStatusDescription(NewStatus status); string GetStatusDescription(AciveStatus status); string GetStatusDescription(DeactivatedStatus status); } ` Not sure if this will render correctly! – Dib Feb 21 '17 at 15:53
2

The visitor pattern does not have to represent commands. That most examples of the visitor pattern have no return type is entirely a restriction of the C++ type system and not an integral part of the pattern. In languages without a static type system (like Perl, Python, JS, Ruby) or with type-erasure based generics (like Java, C#) can we define a generic visitable object as:

// Java, but C# works similarly

interface Visitable {
  <T> T accept(Visitor<? extends T> visitor);
}

interface Visitor<T> {
  T visitA(A a);
  T visitB(B a);
}

class A implements Visitable {
  @Override
  public <T> T accept(Visitor<? extends T> visitor) {
    return visitor.visitA(this);
  }
}

class B implements Visitable {
  @Override
  public <T> T accept(Visitor<? extends T> visitor) {
    return visitor.visitB(this);
  }
}

...

These Visitable classes can now accept any visitor, including Visitor<String>, Visitor<Integer> or also Visitor<Void> instances.

As a non-generic example of this, your IVisitor is absolutely acceptable. I'd still encourage you to make your visitor generic so that it can be used for other operations as well, not just for getting a status description.

If you have a single-purpose visitor interface, this would indicate that the functionality provided by the visitor should perhaps reside in the target objects instead. So instead of

var description = status.Accept(new StatusDescriptionVisitor());

we might say more directly

var description = status.StatusDescription();

The key difference is that making some class hierarchy visitable allows us to extend that class hierarchy with new methods. This is necessary when we can't foresee all necessary operations, or when the operations are part of different responsibilities. However, if we control all usage of the visitable objects and for fairly small operations like a getter, perhaps a simple method is better than introducing a visitor.

  • Excellent answer with very clear explanation. I never have thought of creating generic interfaces like that, but it makes sense to. My example was very contrived so in a real world example your later examples would also make much sense. Thanks for taking the time to explain. – Dib Feb 21 '17 at 15:51
  • 2
    Though Java uses type erasure for generics, I thought C# doesn't? – Erik Eidt Feb 21 '17 at 17:48
  • @ErikEidt From the view of a programmer, C# doesn't have type erasure – you can freely inspect type parameters at runtime. However, .NET is designed in a way that as an implementation detail the CLR's JIT-compiler can use type erasure. How is this reconciled? The type parameters are passed at runtime as hidden extra arguments. Type erasure avoids having to compile specialized code for each type, but only works for reference types. You can partially simulate this in Java by passing Class<T> arguments to generic functions. – amon Feb 21 '17 at 17:56
  • @amon C# uses type reification, not type erasure. Resorting to an "implementation detail" seems beyond what's reasonable. Otherwise, we'd be able to claim that Java uses type reification too because, AFAIK, inside the JVM java is still aware of type information (i.e. does not use type erasure). – code_dredd Feb 22 '17 at 15:00
  • @ray You're right about reification for generic types. But for generic methods, C# is much closer to Java than to C++. Generic virtual methods are possible with either type erasure (only for reference types) or with JIT compilation, and .NET CLR happens to do both. Here, type erasure means that machine code for a generic method can be reused for multiple types. I'm sorry I didn't properly distinguish between generics on types and methods in my above comment. – amon Feb 22 '17 at 15:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.