Wednesday, March 28, 2012

Common uses for the State pattern

Of all the design patterns, I think the state pattern is one of the simplest and probably the one I use the most. It's easy to understand and easy to use but I still see many places where it could be used but isn't. Here's a few things that indicate when using state objects would be simpler.

If you have a variable that represents what 'mode' or 'state' something is in, or what the current behavior is, then you may want to use the state pattern. A common place for this is if you have a gui and the user can interact with one of many tools. Instead of:

public void onClick(int x, int y){
  if (currentTool == PAINT)
    canvas.paint(x, y);
  else if (currentTool == BLUR)
    canvas.blur(x, y);
  else if (currentTool == ERASE)
    canvas.erase(x, y);
  else if (currentTool == LINE && lineStart == null)
    lineStart = new Point(x, y);
  else if (currentTool == LINE && lineStart != null) {
    canvas.drawLine(lineStart.x, linesStart.y, x, y);
    lineStart = null;
  }
}

you could refactor to the state pattern:

public void onClick(int x, int y){
  currentTool.onClick(x, y);
}

class PaintTool extends GuiTool {
  @Override
  public void onClick(Canvas canvas, int x, int y){
    canvas.paint(x,y);
  }
}

This means that new tools could be added without having to change the gui code. The state pattern would also simplify the gui since it no longer needs to determine what clicking means. This is especially beneficial when multi-click operations are involved since the gui no longer needs extra variables to track that.

class LineTool implements GuiTool {

  private Point startPoint;

  @Override
  public void onClick(Canvas canvas, int x, int y){
    if (startPoint == null)
      startPoint = new Point(x, y);
    else {
      canvas.drawLine(startPoint.x, startPoint.y, x,y);
  }
}

That brings to another common case where the state pattern is useful: If you have variables that are used in some situations and ignored in others, then you may want to use the state pattern instead. For example, if you have a game or simulation where each agent either has a fixed home, another agent it follows, a group it considers "home", then instead of:

public void goHome(){
  if (homeLocation != null)
    goTo(homeLocation.x, homeLocation.y);
  else if (leader != null)
    follow(leader);
  else if (group != null)
    follow(group.getRandomMember());
  else
    wander();
}

you could refactor to the state pattern:

public void goHome(){
  home.goTo();
}

public class StationaryHome implements AgentHome {
  private Agent agent;
  private Point home;

  public StationaryHome(Agent agent, Point home){
    this.agent = agent;
    this.home = home;
  }

  @Override
  public void goTo(){
    agent.goTo(home);
  }
}

Once you have the different states, then the agent no longer needs all of those extra variables and is made much simpler. It also disallows states that don't make sense: what does it mean if an agent has a location it considers home and a group it considers home? If that's not a valid situation then you need to make sure you never end up with both. If it is a valid situation then you can just create a new state object that handles it.

You may have noticed something about these two examples: If you have a bunch of if/if else statements that compare variables to literal values (like enums!), then you may want to use the state pattern instead. Complex objects with complex logic can be greatly simplified by using the state pattern. In fact; it's possible to completely remove all branching and delegate behavior to multiple different state objects instead.

Since these scenarios are all cases where we want different behavior based on some runtime values, they're good candidates for the state pattern. So keep an eye out for chances to use the state pattern.

No comments:

Post a Comment