The accepted solution is fine and is a concrete solution to your problem. However, I would like to posit an alternative, more abstract solution.
In my experience, the use of enums to define the flow of logic is a code smell as it is often a sign of poor class design.
I ran into a real world example of this happening in code that I worked on last year. The original developer had created a single class which did both import and export logic, and switched between the two based on an enum. Now the code was similar and had some duplicate code, but it was different enough that doing so made the code significantly more difficult to read and virtually impossible to test. I ended up refactoring that into two separate classes, which simplified both and actually let me spot and eliminate a number of unreported bugs.
Once again, I must state that using enums to control the flow of logic is often a design problem. In the general case, Enums should be used mostly to provide type-safe, consumer-friendly values where the possible values are clearly defined. They're better used as a property (for example, as a column ID in a table) than as a logic control mechanism.
Let's consider the problem presented in the question. I don't really know the context here, or what this enum represents. Is it drawing cards? Drawing pictures? Drawing blood? Is order important? I also do not know how important performance is. If performance or memory is critical, then this solution is probably not going to be the one you want.
In any case, let's consider the enum:
// All possible combinations of One - Eight.
public enum ExampleEnum {
One,
Two,
TwoOne,
Three,
ThreeOne,
ThreeTwo,
ThreeOneTwo
}
What we have here are a number of different enum values which are representing different business concepts.
What we could use instead are abstractions to simplify things.
Let us consider the following interface:
public interface IExample
{
void Draw();
}
We can then implement this as an abstract class:
public abstract class ExampleClassBase : IExample
{
public abstract void Draw();
// other common functionality defined here
}
We can have a concrete class to represent Drawing One, two and three (which for the sake of argument have different logic). These could potentially use the base class defined above, but I'm assuming that the DrawOne concept is different from the concept represented by the enum:
public class DrawOne
{
public void Draw()
{
// Drawing logic here
}
}
public class DrawTwo
{
public void Draw()
{
// Drawing two logic here
}
}
public class DrawThree
{
public void Draw()
{
// Drawing three logic here
}
}
And now we have three separate classes which may be composed to provide the logic for the other classes.
public class One : ExampleClassBase
{
private DrawOne drawOne;
public One(DrawOne drawOne)
{
this.drawOne = drawOne;
}
public void Draw()
{
this.drawOne.Draw();
}
}
public class TwoOne : ExampleClassBase
{
private DrawOne drawOne;
private DrawTwo drawTwo;
public One(DrawOne drawOne, DrawTwo drawTwo)
{
this.drawOne = drawOne;
this.drawTwo = drawTwo;
}
public void Draw()
{
this.drawOne.Draw();
this.drawTwo.Draw();
}
}
// the other six classes here
This approach is far more verbose. But it does have advantages.
Consider the following class, which contains a bug:
public class ThreeTwoOne : ExampleClassBase
{
private DrawOne drawOne;
private DrawTwo drawTwo;
private DrawThree drawThree;
public One(DrawOne drawOne, DrawTwo drawTwo, DrawThree drawThree)
{
this.drawOne = drawOne;
this.drawTwo = drawTwo;
this.drawThree = drawThree;
}
public void Draw()
{
this.drawOne.Draw();
this.drawTwo.Draw();
}
}
How much simpler is it to spot the missing drawThree.Draw() call? And if order is important, the order of the draw calls is also very easy to see and follow.
Disadvantages to this approach:
- Every one of the eight options presented requires a separate class
- This will use more memory
- This will make your code superficially larger
- Sometimes this approach is not possible, though some variation on it might be
Advantages to this approach:
- Each of these classes is completely testable; because
- The cyclomatic complexity of the draw methods is low (and in theory I could mock the DrawOne, DrawTwo or DrawThree classes if necessary)
- The draw methods are understandable - a developer does not have to tie their brain in knots working out what the method does
- Bugs are easy to spot and difficult to write
- Classes compose into more high level classes, meaning that defining a ThreeThreeThree class is easy to do
Consider this (or a similar) approach whenever you feel the need to have any complex logic control code written into case statements. Future you will be happy you did.
goto
when the high level structure does not exist in your language. I sometimes wish there was afallthru
; keyword to get rid of that particular use ofgoto
but oh well.goto
in a high-level language like C#, then you've likely overlooked many other (and better) design and/or implementation alternatives. Highly discouraged.