States That Matter

IceI’ve been spending a little time reviewing design patterns recently, so I thought I would post an example I’ve been working on for the State design pattern. Essentially, the pattern makes it easy for you to change behavior of an object as its state changes.

I didn’t want to copy someone else’s example, so I hope you find this one interesting.

I’m going to create a Water class that has three different states: solid, liquid, and gas. The only difference in behaviors for the states will be their descriptions, which change as the temperature of the water changes. Of course, I don’t think anyone would ever utilize the state pattern for such a simple case, but I’m hoping that it is easy to follow.

The first class I’ll introduce is the Water class. It has an internal state that tracks the temperature and description of the water.

class Water : Printable {
    
    private var state: State! = nil
    
    init() {
        state = Liquid(temperature: 105, water: self)
    }
    
    var temperature: Double {
        get {
            return state.temperature
        }
        set(value) {
            state.temperature = value
        }
    }
    
    var description : String {
        get {
            return state.description
        }
    }
}

 
Next, we’ll need the State class. It provides scaffolding for the various states and also includes some logic that can be shared among states.

class State : Printable {
    
    var water: Water
    
    init(water: Water){
        self.water = water
    }
    
    private(set) var temperature: Double = 0.0 {
        didSet {
            stateChangeCheck()
        }
    }
    
    var description : String {
        get {
            fatalError("\(__FUNCTION__) requires override by subclass.")
        }
    }
    
    private func isSolid() -> Bool {
        return temperature <= 32
    }
    
    private func isLiquid() -> Bool {
        return temperature > 32 && temperature < 212
    }
    
    private func isGas() -> Bool {
        return temperature >= 212
    }
    
    private func stateChangeCheck() {
        fatalError("\(__FUNCTION__) requires override by subclass.")
    }
}

 
Finally, we implement the three states.

class Liquid : State {
    
    init(temperature: Double, water: Water) {
        super.init(water: water)
        self.temperature = temperature
    }
    
    override var description : String {
        get {
            return "Liquid"
        }
    }
    
    private override func stateChangeCheck() {
        if isSolid() {
            water.state = Solid(temperature: temperature, water: water)
        } else if isGas() {
            water.state = Gas(temperature: temperature, water: water)
        }
    }
}

class Solid : State {
    
    init(temperature: Double, water: Water) {
        super.init(water: water)
        self.temperature = temperature
    }
    
    override var description : String {
        get {
            return "Solid"
        }
    }
    
    private override func stateChangeCheck() {
        if isLiquid() {
            water.state = Liquid(temperature: temperature, water: water)
        } else if isGas() {
            water.state = Gas(temperature: temperature, water: water)
        }
    }
}

class Gas : State {
    
    init(temperature: Double, water: Water) {
        super.init(water: water)
        self.temperature = temperature
    }
    
    override var description : String {
        get {
            return "Gas"
        }
    }
    
    private override func stateChangeCheck() {
        if isSolid() {
            water.state = Solid(temperature: temperature, water: water)
        } else if isLiquid() {
            water.state = Liquid(temperature: temperature, water: water)
        }
    }
}

 
I’ve put all of these classes in the same file so that I can use the “private” keyword like I would use the “protected” keyword in other languages. Doing this makes it possible to expose temperature and description, without exposing things I don’t want to expose, like state. Remember, “private” is a little different in Swift than it is in other languages: it limits access to code in the same file (not the same class.)

Let’s see how it works:

var water = Water()
println(water.temperature) // 105.0
println(water.description) // Liquid

water.temperature -= 115
println(water.temperature) // -10.0
println(water.description) // Solid

water.temperature += 225
println(water.temperature) // 215.0
println(water.description) // Gas

 
And there you have it!

Share