I just had a dilemma today that called for a solution better than using if or case statements. After some thought, probably too much thought (my excuse is I'm tired), I determined that the Visitor pattern could do a much better job without being a maintenance nightmare.
My situation involved a registration process in a client-server architecture where users would be registered with dock doors to receive events relating to shipments and receipts. The user would register and then register the appropriate dock doors that he/she wanted to monitor. The object model ended up looking something like this:
RegistrationRequest
|
+--- RegisterUserRequest
|
+--- UnregisterUserRequest
|
+--- RegisterDockDoorRequest
|
+--- UnregisterDockDoorRequest
The client submits the registration request to the server via remoting. The server receives the request and performs the necessary action based on the type of request being processed. That last statement is the key to the problem and the solution to the same problem.
How do you determine if a received argument of type RegistrationRequest is a RegisterUserRequest or a UnregisterUserRequest for example? The first logical solution might be to define four separate methods called ProcessRequest that take an argument of each of the concrete classes. Unfortunately, that doesn't work, because .NET does not support contra-variance (except with delegates at 2.0 anyway).
So now what. I really didn't want to resort to something like this:
if (request is RegisterUserRequest)
// ...
else if (request is UnregisterUserRequest)
// ..
else if (request is RegisterDockDoorRequest)
// ..
else if (request is UnregisterDockDoorRequest)
// ...
else
throw new NotSupportedException();
The class where this logic would be contained is not likely to be duplicated anywhere else, so in all rights it may not matter. However, it just was sloppy to me. And I didn't want to do it if or no other reason than the performance hit by repeated casts.
I'll now explain why the statement “The server receives the request and performs the necessary action based on the type of request being processed” is also the solution to my problem. When I ran into this design roadblock I started thinking about my object model and patterns in general. I knew this had to be a problem in search of a pattern.
So, I pulled out my handy Design Patterns book and started looking at the intents of the behavioral patterns -- since behavior is what I'm interested in. I came across this intent for the Visitor pattern:
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
That sounds allot like my problem. And in fact after reviewing the applicability I was sold on it. I quickly implemented the pattern and viola the problem is solved. So what did things look like after implementing the pattern?
The first step was creating the Visitor classes. I decided to go with a interface and concrete implementation instead of a base class and concrete implementation, because I didn't see that there would be any shared behavior. My Visitor interface looks like this (irrelevant stuff removed):
public interface IRegistrationRequestVisitor
{
// ...
void VisitRegisterUserRequest(RegisterUserRequest request);
void VisitUnregisterUserRequest(UnregisterUserRequest request);
void VisitRegisterDockDoorRequest(RegisterDockDoorRequest request);
void VisitUnregisterDockDoorRequest(UnregisterDockDoorRequest request);
}
I won't show the concrete implementation of the IRegistrationRequestVisitor, because it doesn't add anything to my explanation. The next step was to add the abstract method to the RegistrationRequest implementation and to the concrete subclasses that utilize this. The RegistrationRequest class and its concrete subclasses are called “Elements” as far as the pattern is concerned. Here is what the modified classes look like (I'm only showing the relevant portions of the RegistrationRequest and RegisterUserRequest classes for brevity):
public abstract class RegistrationRequest : MarshalByRefObject
{
// ...
public abstract void Accept(IRegistrationRequestVisitor visitor);
}
public sealed class RegisterUserRequest : RegistrationRequest
{
// ...
public override void Accept(IRegistrationRequestVisitor visitor)
{
if (visitor == null)
throw new ArgumentNullException(“visitor“);
visitor.VisitRegisterUserRequest(this);
}
}
I was thoroughly happy with this design. What it meant for the implementation in my server component was that I could use the following code to perform my registration instead of the aforementioned if else mess (request is an argument of type RegistrationRequest):
GenericRegistrationRequestVisitor visitor = new GenericRegistrationRequestVisitor(...);
request.Accept(visitor);
The consequence for this pattern is that you have to define a new operation in the Visitor interface and all its concrete subclasses anytime you have to add a new concrete element class. In my implementation the object model should be stable, so this shouldn't be an issue. However, I would argue that the maintainability of the if else mess would be a worse problem to have.
Patterns are a great weapon to have in the arsenal and I continually find occasions to use them. The completed implementation of this problem also included use of the Builder pattern, but I have not included that so that I could focus on the Visitor pattern.