Imagine that you need to develop an application for shipping Orders
. Your Orders
could be in few states: New Order
, Registered
, Granted
, Shipped
, Invoiced
, Cancelled
.
And there are some rules which allow your Order
to migrate to another state. This means that for example you cannot ship not registered Order
. Also, there are some behavioral rules. For example, you cannot add new Product
to your Order
when it is in Cancelled
state. Also, behavioral rules could mean changes in state.
How could you accomplish these requirements easily?
STATE
Allowable States
To be more clear about the behavior of our Order
, let's take a look at the next statechart diagram:
We could encapsulate behavior which belongs to the current object's state into separate classes derived from some parent class. Each of the concrete will be responsible for allowing changing state from one to another.
State Pattern UML
State Pattern UML in Application to Order Problem
How Does It Work?
At first, our Order
class has a reference to its _state
, to have more real example, Order
also has _products
.
public class Order {
private OrderState _state;
private List<Product> _products = new ArrayList<Product>();
public void setOrderState(OrderState state){
_state = state;
}
Order
delegates some state-specific requests to the current state.
public void ship(){
_state.ship();
}
If the current state is Granted
, it changes Order
's state to Shipped
and if needed, does some surrounded work or delegates it.
This code represents concrete implementation of the OrderState
. Constructor of base class has Order
parameter, so this allows us have reference to holding Order
: _order
in derived classes.
public class Granted extends OrderState{
public Granted(Order holdingOrder) {
super(holdingOrder);
}
public void ship(){
_order.doShipping();
_order.setOrderState(new Shipped(_order));
}
If you care what method ship()
is doing in Order
class, it could be like shipping
:).
public void doShipping(){
System.out.println("Shipping...");
}
If current state is Registered
, most likely that class has no implementation of method ship()
, it has only addProduct(), grant(), and cancel()
. So method of super class will be called. OrderState
has all bulk of methods but they all throw exceptions, which says that changing state in current situation is not allowed.
public class OrderState {
protected Order _order;
public OrderState(Order holdingOrder){
_order = holdingOrder;
}
public void ship(){
raiseNotAllowableOperation("ship");
}
private void raiseNotAllowableOperation(
String operation) throws IllegalStateException {
String stateName = this.getClass().getName();
System.out.println(
"ERROR: Operation ["+operation+"] is not allowed for current order state: " +
stateName);
}
Example of Usage of Order Class
Now we navigating to customer
code. Our OrderingSystem
creates new Order
, adds some beer
as product
and all in correct way:
public void doHardWork(){
Product beer = new Product();
beer.Name = "Lvivske";
beer.Price = 78000;
Order order = new Order();
order.printStateName();
order.addProduct(beer);
order.printStateName();
order.register();
order.printStateName();
order.grant();
order.printStateName();
order.ship();
order.printStateName();
order.invoice();
order.printStateName();
}
Output
Order state: designpatterns.state.NewOrder
Product addeding calculation...
Order state: designpatterns.state.NewOrder
Registration...
Order state: designpatterns.state.Registered
Granting...
Order state: designpatterns.state.Granted
Shipping...
Order state: designpatterns.state.Shipped
Invoicing...
Order state: designpatterns.state.Invoiced
Ok, now we added code, which tries to add more beer
when order
has been already shipped.
order.ship();
order.printStateName();
order.addProduct(beer);
order.printStateName();
Output
Order state: designpatterns.state.NewOrder
Product addeding calculation...
Order state: designpatterns.state.NewOrder
Registration...
Order state: designpatterns.state.Registered
Granting...
Order state: designpatterns.state.Granted
Shipping...
Order state: designpatterns.state.Shipped
ERROR: Operation [addProduct] is not allowed for current order state:
designpatterns.state.Shipped
Other Ways to Solve Problem Without State Pattern
One of the disadvantages of this you could see many of the classes needed to have:
Yes, but this is the way to greatly encapsulate your behavior. Also, there are other ways to resolve the problem. For example, usage of the table [state|method|state]
which is populated with all allowable transitions. You could also resolve the issue with having switch
-case
method. About all of this, you could read in Jimmy Nilsson's book "Applying Domain-Driven Design and Patterns".
What Have I Learned?
- Java has no "
virtual
" keyword, that is because all methods are virtual
. Make senses cause in C#, it is often needed to write huge amount of that word. - I discovered few Exceptions that Java has, for example,
IllegalStateException
. - I was not able to easily figure out what to use instead of "
:
" in declaration of derived classed. Now I know "extends
". - I improved my UML skills with practicing drawing them.
I hope you enjoyed this story.
Go to: My Design Patterns Table