Home > Articles > Programming

  • Print
  • + Share This
This chapter is from the book

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.

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.

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 and ConcreteStateB). 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

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.

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.

  • + Share This
  • 🔖 Save To Your Account

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020