Saturday 29 October 2011

OO Enums

I was looking for feedback on my previous post about Enum Visitors.

I was literally inundated with feedback from 1 person:

The Article lacks the why

and

Why would i want to do this if an enum is:

  • What I know
  • Easy to understand
  • Performs well

Clearly this looks like many reasons not to fuss.

There are even more things in favour of the enum like the bitwise operations.

I welcome this criticism because I, much like other programmers often fixate on a certain solution or implementation and forget what the original problem was or even if there really was one and why we wanted to solve it in the first place.

I do not advocate to add complex patterns to your code base just for the sake of using the pattern.

And even though I think a part of me  was trying to solve a problem that perhaps isn’t such a big problem but to see if I could come up with something decent.

So lets look at the reasons NOT to use my method.

It’s added complexity for some.

Enums are FAST, and I mean VERY fast, got say 1-5 enums even with the most tweaked dictionary the switch statement will be much faster.

You may run into a scenario where you have 100ds of enums where the performance goes the other way, but in this case you probably wont be using enums anyway.

And I can possibly think of scenarios like high speed logging, where you might want to think a bit more about how your performance may be affected

That’s about the last i will say about the performance, because I feel that unless you are running VERY tight loops with 1000ds of iterations this will be a fraction of your programs execution time for either of these methods.

Many people reduce OO/Layers or abstractions to gain performance and there are times you simply have no other choice, however I feel that favouring ease of use and maintenance over performance where acceptable is often a better choice.

But either way there might be situations I will be the first to say DON’T use this way.

But lets look at why this might be a good idea.

Having a switch statement you may introduce many pathways through your code known as Cyclomatic Complexity.

This means it becomes hard to test the function entirely without stimulating it with ALL the options the enumeration provides.

If we invert this operation and instead of switching over an enum we use a visitor or strategy map it is a little bit like the Hollywood Principle (Don’t call us, We’ll call you)

In fact if you think about it this is one of those really cool and catchy terminologies, but often it is described in a way that is hard to understand.

And I don’t see in the description that they refer to this or the visitor even though i feel this is appropriate.

I have read MANY places that people constantly refer to OCP (Open-Close principle), and how the switch statement violates this.

And  I somewhat agree, when you switch around many conditions, as soon as 1 or more of those conditions change the code that is switching around them needs change.

This means the system must change and cannot just be extended by adding a strategy class or a visitor method.

However you still need to make a change the only difference is where. If you have added switch logic over the same enum on many places then changing an enum changes the code in all those places.

The difference between the visitor/strategy and the switch statement is that with the switch statement it is YOU the caller or consumer that needs change. YOU need to decide which type you are dealing with and write the if-else or switch logic to control the flow of the code, you may do this MANY times and this increases your responsibility of future change instead of just being concerned of the business logic of your program.

Using a strategy or visitor you simply call the Accept method or execute on the strategy and can only focus on the business logic you should be concerned of.

The flow of execution is inverted and this is the important concept to understand.

Of course this is just one abstraction, the decision is made somewhere and when the enum changes so does the somewhere. So you may have a situation where you only need to make a code decision for your application in ONE place.

Take this example:

public enum MessageType
{
Unknown,
Message,
Success,
Highlight,
Error
}

Say this is just a simple enumeration to use in our code to log certain status messages now we could switch around the code like this:

Would result in code such as this:

void LogMessageWithEnum(MessageType messageType, string message)
{
switch (messageType)
{
case MessageType.Message:
Console.ForegroundColor = ConsoleColor.White;
break;

case MessageType.Success:
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(message);
Console.ForegroundColor = ConsoleColor.White;
break;

case MessageType.Highlight:
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(message);
Console.ForegroundColor = ConsoleColor.White;
break;

case MessageType.Error:
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(message);
Console.ForegroundColor = ConsoleColor.White;
break;
}

If this is converted to a visitor:

public class MessageTypeVisitor
{
public MessageTypeVisitor(string message, bool newLine)
{
this.Message = message;
this.NewLine = newLine;
}

void LogMessage(string message)
{
if (NewLine)
Console.WriteLine(this.Message);
else
Console.Write(this.Message);
}



[EnumVisitor(MessageType.Message)]
public void LogMessage()
{
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(this.Message);
}

[EnumVisitor(MessageType.Success)]
public void LogSuccess()
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(this.Message);
Console.ForegroundColor = ConsoleColor.White;
}

[EnumVisitor(MessageType.Highlight)]
public void LogHighlight()
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(this.Message);
Console.ForegroundColor = ConsoleColor.White;
}

[EnumVisitor(MessageType.Error)]
public void LogError()
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(this.Message);
Console.ForegroundColor = ConsoleColor.White;
}

public string Message { get; set; }

public bool NewLine { get; set; }
}

Using this will become as follows:

messageType.Accept(new MessageTypeVisitor(formattedMessage, newLine));

Now your code becomes more open-close, you could be asking an abstract factory or IoC container to provide you with a MessageTypeVisitor meaning your code would never have to change its logic based on what enumeration was provided.

Of course all this really is, is an abstraction, I can create a function LogMessageWithEnum and be done with it. My code that uses the LogMessageWithEnum is shielded from the underlying switch and decision making, and in some cases this may be the only place I use this enum, My code may not use this enum for any other reason. So by simply using a function I have created a sufficient abstraction and remain closer to Open-Close to my consuming code.

The problem really comes in when I have these same switch statements scattered around making the same or very similar decisions around this enumeration, in this case I haven't sufficiently abstracted myself from the use of this.

In conclusion

I wanted to create something for those times where you are looking for this type of solution. But not replace the enum or usage of this sparingly or in a way that makes sense. As always let your own common sense guide your solution.

No comments:

Post a Comment