Friday, December 16, 2005

The beauty of the State (Pattern)

Ok, let's talk about design, please.

First, recall what said in my previous post:

Say the boost() method, depending on the GearType, must do the following:

  • Decreasing Car fuelQuantity and oilQuantity.

  • Calling changeGear() on TransmissionGear

How would you do this?

For a moment, I'll talk about the straightforward way: if you have a boost() method on the Car object, which must behave differently depending upon a property of some associated object, simply code a sequence of if statements upon this property.
So, if GearType is FIRST, change fuelQuantity and oilQuantity accordingly and change gear to SECOND; if it is SECOND, change them in a different way and change gear to THIRD; and so on.

You can code this in minutes, but if you do so you'll have a lot of problems.
Your code has a lot of if statements, is not so object oriented and is hard to read.
Moreover, the biggest problem is that if you must add another gear, you'll have to directly modify the boost() method for add another if!
You are changing some code that actually works ... what happens if your change breaks something?!

The best would be to completely isolate the various boost() algorithms, and the adding of another gear, that is, another algorithm for boost().

A good way to do so is applying the State design pattern.

You'll have to simply:

  1. Encapsulate the Car properties, changed by the boost() method, into a value object (LiquidQuantity, sorry for the stupid name) : this prevents from directly changing Car properties.
    Note that this is an optional step, because you could simply insert two getters/setters into the Car class, but in this case I'd suggest you to make this
    setters package-protected.

  2. Create a "state" class for every gear type, implementing a common interface (GearState): it represents the changing state.

  3. Implement, in every GearState, a boost() and a changeGear() method: the former must implement the algorithm which changes the LiquidQuantity, the latter must determine the next gear.

  4. Associate the TransmissionGear with all GearState objects through a Map, whose keys are the various GearType: doing so, the TransmissionGear can access the GearState using its GearType property, representing the current gear.

  5. Implement a boost() method in TransmissionGear, which delegates to its current GearState for the appropriate boost() and changeGear() behaviour.

  6. Implement the boost() method in Car, making a call to the boost() method on TransmissionGear.

This is a class diagram with some meaningful comments and Java code snippets:

Maybe I should show you an interaction diagram, too, but for now I'm too lazy, maybe in the next post if you care ;)

Using the state pattern, so, you can clearly separate the behaviour that changes in conjunction with the state of some object, and add another state, with another behaviour, without affecting old code: in our example, simply implement another GearState and add it to the TransmissionGear map.
Moreover, the state changing is also absolutely transparent.

This, obviously, at the cost of some more class and some more dependency between classes, but I think that benefits overcome.

As always, have a good design!

No comments: