A Head Start on Domain-Driven Design Patterns

Design Patterns

When I say Design Patterns here, the first thoughts of many will go to the Design Patterns book [GoF Design Patterns], which has been mentioned a whole bunch of times by now. It’s by no means the only book about Design Patterns, but it’s considered the standard work on the subject.

Design Patterns are abstract and pretty low-level in that they are technical and general in regard to domain. It doesn’t matter what tier or what type of system you are building, Design Patterns are still useful.

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 that the patterns there use one or more Design Patterns or that some specific Design Patterns can be applied in a more specific way in those categories.

NOTE

While we’re talking about low-level, here is a fun little story. I was asked what the difference was between me and a friend on a professional level. I said that my friend worked with low-level programming and I worked with high-level programming. The person asking didn’t know anything about programming, but he got that look on his face you get when listening to someone blowing his own trumpet way too much.

The Design Patterns book [GoF Design Patterns] is pretty hard-going. Each time I read it I understand and learn more about it. For example, I have often thought, "That’s not correct" or "That’s not the best solution" or even "That’s stupid." But after some deliberation, I decide that they are "right" each time.

So far there has been a lot of talk and little action. It’s time to become concrete by giving an explanation of a Design Pattern. I have chosen one of my favorite Design Patterns called State, so here goes.

An Example: State Pattern

Problem-based teaching is a good pedagogic approach, so I’ll use it here. The following is a problem.

Problem

A sales order can be in different states, such as "NewOrder," "Registered," "Granted," "Shipped," "Invoiced," and "Cancelled." There are strict rules concerning to which states the order can "go" from which states. For example, it’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.)

One more thing to remember is that certain behavior will lead to state transformation. For example, when AddOrderLine() is called, the state transforms from Granted back to New Order.

Solution Proposal One

In order to solve this problem, I need to describe a state graph in code. In Figure 2-1 you find a very simple and classic state graph describing how state of the button is changed between Up and Down each time the user pushes the button.

Figure 2.1

Figure 2-1 State graph for a button

If we apply this technique of a state graph on the Order, it could look like Figure 2-2.

Figure 2.2

Figure 2-2 State graph for an Order

One obvious solution is probably to use an enum like this:

public enum OrderState
{
    NewOrder,
    Registered,
    Granted,
    Shipped,
    Invoiced,
    Cancelled
}

and then to use a private field for the current state in the Order class, like this:

private OrderState _currentState = OrderState.NewOrder;

Then, in the methods, you need to deal with two things on top of what the methods should do. You must check if the method might be called at all in that state, and you need to consider if a transition should take place and, if so, to what new state. It could 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.
    else
        throw new ApplicationException(...

    //Do the interesting stuff... 
}

As you saw in the code snippet, the method got quite a lot of uninteresting code added just because of taking care of the state graph. An ugly if-statement is very fragile to changes in the future. Code similar to that will be sprinkled everywhere in the Order class. What we 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, we should reduce the code duplication and fragmentation. Let's give it a try.

Solution Proposal Two

Proposal Two is just a slight variation. You can have a private method called _ChangeState(), which could, for example, be called from AddOrderLine(). _ChangeState() could have a long switch statement, like this:

private void _ChangeState(OrderState newState)
{
   if (newState == _currentState)
      return; //Assume a transition to itself is not an error.
      
   switch (_currentState)
   {
      case OrderState.NewOrder:
         switch (newState)
         {
            case OrderState.Registered:
            case OrderState.Cancelled:
               _currentState = newState;
               Break;

            default:
               throw new ApplicationException(...
               break;
         }
      case OrderState.Registered:
         switch (newState)
         {
            case OrderState.NewOrder:
            case OrderState.Granted:
            case OrderState.Cancelled:
               _currentState = newState;
               break;

            default:
               throw new ApplicationException(...
               break;
         }
      ...
      //And so on...
   }

The AddOrderLine() now looks like this:

public void AddOrderLine(OrderLine orderLine)
{
    _changeState(OrderState.NewOrder);


    //Do the interesting stuff... 
}

I was quite lazy in the previous code and only showed the start of the structure of the huge switch statement, but I think it's still pretty obvious that this is a good example of smelly code, especially if you consider that this example was simplified and didn't discuss all aspects or all states that were really needed—not even close.

NOTE

As always, for some situations the example just shown was good enough and the "right" solution. I just felt I had to say that so as to not imply that there is only one solution to a problem that is always right.

Also note that I will discuss some more about code smells in the next chapter.

OK, I’ve been there, done that in several projects. I can’t say I like that solution very much. It seems fine at first, but when the problem grows, the solution gets troublesome. Let’s try out another one.

Solution Proposal Three

The third solution is based on a table (some kind of configuration information) describing what should happen at certain stimuli. So instead of describing the state transformation in code as in proposals one and two, this time we describe the transformations in a table, Table 2-1.

Table 2-1 State Transitions

Current State

Allowed New State

NewOrder

Registered

NewOrder

Cancelled

Registered

NewOrder

Registered

Granted

Registered

Cancelled

...

...


Then your _ChangeState() method can just check if the new state that comes as a parameter is acceptable for when the current state is NewOrder, for example. For the current state NewOrder, only Registered and Cancelled are allowed as a new state.

You could also add another column as shown in Table 2-2.

Table 2-2_State Transitions, Revised

Current State

Method

New State

NewOrder

Register()

Registered

NewOrder

Cancel()

Cancelled

Registered

AddOrderLine()

NewOrder

Registered

Grant()

Granted

Registered

Cancel()

Cancelled

...

 

...


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 is clean and simple. A big advantage here is that it’s very easy to get an overview of the different possible state transformations. The main problem is probably 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 not very good. 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, for example.

Do we have more ideas? Let’s apply some knowledge reuse and try out the Design Pattern called State.

Solution Proposal Four

The general structure of the State pattern is shown in Figure 2-3.

The idea is to encapsulate the different states as individual classes (see -ConcreteStateA andConcreteStateB). Those concrete state classes inherit from an abstract State class. Context has a state instance as a field and calls Handle() of the state instance when Context gets a Request() call.Handle() has different implementations for the different state classes.

Figure 2.3

Figure 2-3 State pattern, general structure

That’s the general structure. Let’s see what this could look like if we apply it to the problem at hand. In Figure 2-4, you find a UML diagram for the specific example.

Figure 2.4

Figure 2-4 State pattern, specific example

NOTE

For this example, it might make sense to add another abstract class as the base class forNewOrder, Registered, and Granted. The new class would implement AddOrderLine() andCancel().

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 not make 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, like this:

internal OrderState _currentState = new NewOrder(this);

Note that the field is declared as internal. The reason for this is so that the state class can change the current state by itself, so Order delegates the state transformations totally to the different state classes. (I could also let OrderState be an inner class of Order to avoid the need for internal.)

This time, the Register() method on Order is extremely simple. It could look like this:

public void Register()
{
    _currentState.Register();
}

The Register() method on NewOrder is also pretty simple. At least it can focus on its own state, and that makes the code clean and clear. It could look like this:

public void Register()
{
    _parent._Register();
    _parent._currentState = new Registered(_parent);
}

Before changing the state, there was kind of a callback to the parent (_parent._Register()) telling it to do its thing before the state was changed. (Note that the "callback" went to the internal method_Register() and not the public Register().) This is just one example of an option, of course. Other examples would be to put the code in the OrderState base class or in the NewOrder class itself. It should go wherever it’s best located.

As you saw, if I want to do things before or after the state transformation, it’s simple and very well encapsulated. If I want to disallow a certain transformation in the NewOrder class, I just skip implementing that method and use the implementation of the base class OrderState for that method. The implementation of the base class throws an exception saying it was an illegal state transformation, if that’s the wanted behavior. Another typical default implementation is to do nothing.

NOTE

If you need more context-aware exceptions, you can of course implement the methods in the subclasses just as well, even if all they will do is raise exceptions.

This also implies that instead of using a base class for OrderState you could use an interface instead. I guess that if the GoF book had been written today, many of the patterns would have used interfaces instead of (or at least together with) abstract base classes. The State pattern isn’t the most typical example of this, but it still is a possible example.

More Comments

When using the State pattern, we were actually swapping a single field into a bunch of separate classes. That doesn’t sound like a very good idea at first, but what we then get is the nice effect of moving 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 we use a solution such as State.

Which solution you prefer is indeed up for debate, but I think the State pattern is one that should be seriously considered here. 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 very easy to overuse, as is every tool. Use it wisely!

That was an example of a Design Pattern, a generic one. We’ll come back to more Design Patterns of another family, but first a discussion about another category of patterns.

转载于:https://my.oschina.net/tantexian/blog/657317

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值