Manage State With The State Pattern
A site's VirtualPathManager acts as a dispatcher for requests. In addition to invoking your VirtualFile objects, the path manager can also pass requests to the default ASP.NET path manager.
- By Jimmy Nilsson
Technology Toolbox: C#
Developers constantly face new design problems.
Yes, we always solve the problems, but sometimes we find that we have backed ourselves into a corner. Sometimes we find that the solution we arrive at has serious drawbacks—and sometimes we create bad solutions. By reusing good, well-proven, and flexible solutions, we minimize these risks and reach the desired results faster.
Patterns are an important tool developers can take advantage of to minimize these risks. Thanks to the higher abstraction levels made possible by patterns, we can also start and succeed with even larger design problems. Pattern awareness also leads to better understanding between developers—system development is highly dependent on good communication.
Patterns provide simple, elegant solutions to recurring design problems. The key advantages patterns provide are flexibility, modularity, and the ability to create understandable and clear designs. Note that I skipped mentioning reusability, although that might be an inappropriate oversight. Patterns take away focus from code reuse and move the focus to knowledge reuse instead. So, patterns are about reusability, as well, but not in the way developers usually think about reuse.
An important point about patterns is that they aren't invented, but rather harvested or distilled; they're proven solutions. But the solution part is just one piece of a pattern. There are actually three pieces: the context, the problem, and the solution.
Before getting started, let's take a generic tour of the concept of patterns and why you should learn about them. In this article, I won't reuse old explanations, but will provide my own view of the patterns. For instance, the example I will describe is Domain Model-focused, which is not typically the case when it comes to Design Patterns. In talking about the practice and theory of implementing Design Patterns, I will also take an iterative approach to the solution (rather than walking you through the final solution only), as you would in real-life scenarios, where you perfect the process over time.
Learning from your mistakes is a powerful technique. But from time to time, it's nice to take a shortcut by studying other people's amassed knowledge, which is a good reason for learning patterns. Let's see if we can find other reasons.
The most obvious reason is probably that patterns are good abstractions that provide building blocks for system design.
If a development team is pattern-conscious, patterns become an important part of the language. Instead of having to describe each and every design idea in minute detail, it's often enough to say a pattern name and everybody in the team can evaluate whether or not it's a good idea for that particular problem. Adding patterns to a team's language might be the single most important reason for embracing patterns, because the common understanding, richness, and expressiveness of the language increase.
Another reason I like patterns is that being able to utilize patterns is a long-lasting skill. For instance, I learned SQL in around 1988 and I can still make a good living from just working with it. The related products and platforms I work with have changed several times, but the underlying concepts remain the same. Patterns are similar.
The principles of design patterns can be debated within certain contexts. They should be used as a guide only, not as an "absolute truth." For example, OCP can easily be over-applied. You might have come to a point where you understand better how a method should be implemented and want to modify it rather than extend it. In that case, adding a method to a class could also be seen as an extension.
Patterns are not only great with up-front design, they are useful during refactoring.
Don't Overdo Patterns
I see little reason for not learning about patterns, but there is at least one commonly occurring side effect to watch out for. Specifically, some developers new to using patterns feel compelled to squeeze 17 patterns into each and every solution. This attitude can present problems, but doesn't typically linger.
What might persist is the risk of over-design. Developers may not insist on 17 patterns, but they might persist in finding the exact, specific pattern. For example, a friend of mine told me about a recent design problem he had discussed with a group of developers at a company. It took my friend three minutes to come up with a simple solution to the problem - the problem itself was a simple one - yet the other developers weren't happy with the solution, so they spent three days of hard thinking to get it just right.
Been there, done that. In my opinion, Test-Driven Development (TDD) is a good technique for avoiding that over-design problem. The design focus will be much more on solving the problem at hand and nothing else, and patterns will be introduced only when needed, through refactoring.
Also, you might have the impression that the patterns concept is a silver bullet. It's not; it's merely another tool for the developer's toolbox.
Patterns are often perceived individually as being "simple," but they get complex in context and in combination with other patterns. I don't remember how many times I've heard people in the newsgroups say something like, "I understand pattern X, but when I try to use it in my application together with pattern Y and pattern Z, and it's extremely complex. Please help!" People need to remember that Design Patterns are abstract and low-level; they are technical and general only in regard to domain.
One way to describe Design Patterns is that they are about refining the subsystems or components. You will find when we move to the other two categories I'm about to discuss here, that it's common practice to use one or more Design Patterns, or that some specific Design Patterns can be applied in a more specific way in those categories.
Executing on the theory
It's time to put some of this theory into practice. The first example uses a Design Pattern called State. I think problem-based teaching is a good pedagogic approach, so I'll use it throughout this article.
Assume you have a sales order that can be in several different states, such as "NewOrder," "Registered," "Granted," "Shipped," "Invoiced," and "Cancelled." There are strict rules concerning to which states the order can "go," and from which states it can "come." In my example, for instance, the state's not allowed to go directly from Registered to Shipped.
There are also differences in behavior depending upon the states. For example, when Cancelled, you can't call AddOrderLine() for adding more items to the order. (That also goes for Shipped and Invoiced, by the way.)
Always remember that certain behavior leads to state transformation. For example, when AddOrderLine() is called, the state transforms from Granted back to New Order.
Solve the State Problem
In order to solve this problem, you need to describe a state graph in code (see Figure 1a and 1b). One obvious solution is to use an enum:
public enum OrderState
Next, use a private field for the current state in the Order class:
private OrderState _currentState =
Then, deal with two things on top of what the methods should do. You must check whether the method might be called at all in that state. And you need to consider whether a transition should take place and, if so, to what new state. It might look like this in the AddOrderLine() method:
private void AddOrderLine(OrderLine orderLine)
if (_currentState == OrderState.Registered ||
_currentState == OrderState.Granted)
_currentState = OrderState.NewOrder;
else if (_currentState == OrderState.NewOrder)
//Don't do any transition.
throw new ApplicationException(...
//Do the interesting stuff...
The method gets a good deal of uninteresting code added to the snippet, because you are addressing the requirements of the state graph. An ugly if statement is susceptible to changes in the future. Code similar to this will be sprinkled everywhere in the Order class. What you would do, is spread knowledge of the state graph in several different methods. This is a good example of subtle, but evil, duplication.
Even for simple examples like this, you should reduce the code duplication and fragmentation (see Listing 1). The AddOrderLine() now looks like this:
public void AddOrderLine(OrderLine orderLine)
//Do the interesting stuff...
Here, I have shown only the start of the structure of the huge switch statement. But I think it's still obvious that this code needs some work, especially if you consider that this example was simplified and didn't even address all the various aspects or states that you need.
Note that this solution is good enough for some situations, and so is the "right" solution. There is often more than one solution to a given problem, and the problem you need to solve will dictate the complexity required for the solution. That said, the current implementation seems fine initially, but the solution gets troublesome as the problem grows.
Another approach is to use a table (some kind of configuration information) that describes what happens in response to certain conditions. So, instead of describing the state transformation in code, this time you describe the transformations in a table (see Table 1).
Then your _ChangeState() method can check whether the new state that comes as a parameter is acceptable for when the current state is NewOrder. In this case, you allow only Registered and Cancelled as a new state for the current state, NewOrder. And, you might then also add a new column (see Table 2).
Now your _ChangeState() method shouldn't take the new state as a parameter, but rather the method name instead. Then _ChangeState() decides what the new state should be by looking in the table.
This approach is clean and simple. A big advantage here—it's easy to get an overview of the different possible state transformations. The main problem is that it's hard to deal with custom behavior depending upon the current state, and then to go to one state of several possible states when a method executes. Sure, it's no harder than with proposal two, but it's still far from ideal. You could register information in the table about what delegates (a delegate is like a strongly typed function pointer) should be executed at certain transformations, and you could probably extend that idea to solve the other problems as well, but I think there is a risk that it gets a bit messy during debugging.
Let's apply some knowledge reuse and try out the Design Pattern called State.
Apply a State Pattern
The idea of the State pattern is to encapsulate the different states as individual classes (see Figure 2). The next step is to apply this pattern to this problem at hand (see Figure 3).
In the specific example, the Order class is the Context from the general structure. Again, Order has a field of OrderState, although this time OrderState isn't an enum, but a class. For the sake of refactoring, your old tests might expect an enum, and then you can keep that enum as well (perhaps as a property which implementation inspects what is the current instance in the state inheritance hierarchy), and thereby avoid making changes to the external interface.
A newly created Order gets a new state instance of a NewOrder at instantiation and sends itself to the constructor:
internal OrderState _currentState = new NewOrder(this);
Note that the field is declared as internal. When you do this, the state class can change the current state by itself, so Order delegates the state transformations totally to the different state classes.
This time, the Register() method on Order is extremely simple:
public void Register()
The Register() method on NewOrder is also simple. At a minimum, it can focus on its own state, which makes the code clean and clear:
public void Register()
_parent._currentState = new Registered(_parent);
Before changing the state, you need to perform a callback to the parent class (_parent._Register()) telling it to do its thing before the state is changed. (Note that the "callback" goes to the internal method _Register() and not the public method Register().) This is just one option, of course. Other examples might be to put the code in the OrderState base class, or in the NewOrder class itself. In all instances, it should go wherever it's best located.
As you just saw, if you want to do things before or after the state transformation, it's simple and well encapsulated. If you want to disallow a certain transformation in the NewOrder class, however, you skip implementing that method and use the implementation of the base class OrderState for that method instead. The implementation of the base class throws an exception saying it is an illegal state transformation, if that's the desired behavior. And another typical default implementation is to do absolutely nothing.
If you need more context-aware exceptions, you can implement the methods in the subclasses as well, even if all they will do is raise exceptions. This also implies that instead of using a base class for OrderState, you can use an interface instead.
When using the State pattern, you are swapping a single field into a bunch of separate classes. That doesn't sound like a good idea initially, but what you achieve a nice effect; you move the behavior to where it belongs and good alignment to the Single Responsibility Principle (SRP).
There are drawbacks, of course, and a typical one is that the program can potentially be flooded with small classes when you use a solution such as State.
Which solution you prefer is a matter for debate, but I think the State Pattern is one that you should consider seriously for this kind of circumstance. You might find that it solves your problem with the least amount of duplicated code, and with the responsibility partitioned out into encapsulated and cohesive units—the concrete state classes. But watch out: The State Pattern is also easy to overuse.
This article is excerpted from Chapter 4 of Applying Domain-Driven Design and Patterns: With Examples in C# and .NET [Addison-Wesley Professional, ISBN: 0321268202] by Jimmy Nilsson.