Over a million developers have joined DZone.

About Design Patterns

This post dives into the usefulness of design patterns with an example of the state pattern in action.

Navigate the Maze of the End-User Experience and pick up this APM Essential guide, brought to you in partnership with CA Technologies

Many times, I've seen messy, unmaintainable code, mainly due to the absence of design patterns. But even more often, I've seen design patterns being applied at places where no one will ever value them. Knowing not only when and how to apply design patterns, but also when and where to not use them, is crucial to avoid frustrations at work.

Example: The State Pattern

Say you have the OrderState enum and you do different things on different parts of your code, depending on the value of the enum:

enum OrderState {
    NEW, APPROVED, CANCELLED;
}

This is just an example. It really doesn't mattter what kind of orders or the context. Here, an order is something that changes its state upon some event, i.e. when the order is created, its state is NEW, and when a manager reviews it and says everything is OK, its state is APPROVED.

Now, imagine we need to create a sale for an order. We're coding a method that receives the order and a credit card number and we need to do different things, depending on the state of the order. If the state is CANCELLED, we need to abort the creation of the sale and throw an exception. If the state is APPROVED, we charge the given credit card with the total amount. And if the state is NEW, we need to send an email to the manager with the order, so that it is reviewed as soon as possible.

In code, the structure of the createSaleFromOrder method would be as follows:

void createSaleFromOrder(Order order, String cardNumber) {

    OrderState state = order.getState();

    switch (state) {
    case CANCELLED:
        // abort sale, send error message, throw exception
        // ...
        break;
    case APPROVED:
        BigDecimal total = order.getTotal();
        // charge cardNumber with total, store created sale, adjust stock, etc
        // ...
        break;
    case NEW:
        // send email to manager with order for review
        // ...
        break;
    default:
        throw new IllegalArgumentException("order.state");
    }
}

This seems fine — until we're told to code another method that depends on the order state, i.e. cancelOrder, which will cancel the given order if its state is APPROVED or NEW, but will fail with an exception if its state is CANCELLED.

In code, the structure of the cancelOrder method would be as follows:

void cancelOrder(Order order) {

    OrderState state = order.getState();

    switch (state) {
    case CANCELLED:
        // illegal state, throw exception
        // ...
        break;
    case APPROVED:
    case NEW:
        // do some work to cancel given order
        // ...
        // change order state
        order.setState(OrderState.CANCELLING);
        break;
    default:
        throw new IllegalArgumentException("order.state");
    }
}

Ugh... another switch statement. What will happen when a new method that depends on the order state needs to be created? Most likely, we'll need to add a new switch statement. At the end, there will be many switch statements scattered over the code, in different methods and classes. This is bad because we will have logic that depends on the order state all over the code. Our code will become hard to maintain, as it will be more difficult to find where the state of the order is changed.

What is worse, we might need to define a new state for the order, as per requirements. One example would be when the manager does not approve the order. In this case, we would need to introduce the OrderState.REJECTED state. And we would need to add new behavior to handle this new state in each one of our previous operations, that were scattered all over the code...

So, many years ago, four clever guys noticed this recurring problem and found a nice solution: the state pattern. Here's our example updated, using this pattern:

// Class representing an order, aka the 'context' in state pattern's jargon
public class Order {

    // When an order is created, its initial state is NEW
    private OrderState state = new OrderStateNew();

    public void createSale(String cardNumber) {
        this.state.createSale(this, cardNumber);
    }

    public void cancel() {
        this.state.cancel(this);
    }

    // Changing the order state shouldn't be a public operation
    void setState(OrderState state) {
        this.state = state;
    }
}

This is the Order class, which holds state, and we call it the context.

Now the class that represents the state of the order:

// Order state base class, not visible outside the package
// Defines operations that are dependent on the order state
abstract class OrderState {

    abstract void createSale(Order order, String cardNumber);

    abstract void cancel(Order order);
}

This is the base class of all possible states for the order, and its purpose is to define all the operations that depend on the order state.

One of such states is CANCELLED:

class OrderStateCancelled extends OrderState {

    @Override
    void createSale(Order order, String cardNumber) {
        // abort sale, send error message, throw exception
        // ...
    }

    @Override
    void cancel(Order order) {
        // illegal state, throw exception
        // ...
    }
}

Here the operations are implemented, but they will be valid only when the state of the order is CANCELLED.

Here's the class for the APPROVED state:

class OrderStateApproved extends OrderState {

    @Override
    void createSale(Order order, String cardNumber) {
        BigDecimal total = order.getTotal();
        // charge cardNumber with total, store created sale, adjust stock, etc
        // ...
    }

    @Override
    void cancel(Order order) {
        // do some work to cancel given order
        // ...
        // change order state
        order.setState(new OrderStateCancelled());
    }
}

Same comment here, this implementation will be valid only when the state of the order is APPROVED.

Finally, we're missing the class that represents the NEW state:

class OrderStateNew extends OrderState {

    @Override
    void createSale(Order order, String cardNumber) {
        // send email to manager with order for review
        // ...
    }

    @Override
    void cancel(Order order) {
        // do some work to cancel given order
        // ...
        // change order state
        order.setState(new OrderStateCancelled());
    }
}

This is not a finalized solution. It's just a sketch to show the pattern. There are operations unimplemented, such as approve or reject, and at least one missing state: REJECTED. But the idea is clear: move state-dependent behavior to classes that represent all possible states of the order, and handle state transitions via the setState method of the Order class, from within the appropriate state-dependent operations.

This is a really nice pattern. It allows for new states to be easily introduced (just create a new class for the new state) and eases maintenance a lot (whenever a modification needs to be done, all code is in the state classes). This pattern is definitely one of the most useful ones.

But...

...despite being so useful, it's also one of the least used ones. And when used, chances are that it wasn't needed in the first place, or that it's being so overused that final code is very hard to understand, hence very hard to maintain. Even if there's a real need to use it and you are certain that you'll correctly apply it, you should ask yourself a few questions before applying this or any other pattern:

  • Is introducing a pattern going to be considered valuable by the rest of the team?
  • Does the company where you work value good software design patterns and principles?
  • Does the company where you work value code and design quality?
  • If you introduce a pattern, will you go aginst the tide?
  • What about time? Are you recognized only by how fast you are at completing assigned tasks? In this case, you might complete them as soon as possible, without taking code maintainability, extensibility, and reusability too much into consideration. After all, someone will surely improve it later. (This philosophy seems to be very common in several companies)...

I'm not saying that you shouldn't try to apply good software design patterns and principles, or that you shouldn't put up a fight at your work to try to improve the whole software development process there. All I'm saying is that you should balance the pros and cons, and finally decide whether you'll use your time to introduce a pattern or not.

Sign Up for the Newsletter

Did you enjoy this post? If so, please consider signing up to The Bounds of Java newsletter. I usually write a new post every 2-4 weeks, so if you'd like to be kept in the loop for the next one, I'd really appreciate it if you'd sign up.

Thrive in the application economy with an APM model that is strategic. Be E.P.I.C. with CA APM.  Brought to you in partnership with CA Technologies.

Topics:
java ,design patterns ,state pattern ,clean code

Published at DZone with permission of Federico Peralta Schaffner, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}