Wednesday, November 19, 2008

State Pattern using module extension

I try to favour delegation over inheritance. But sometimes Replace Inheritance with Delegation can be difficult. It turns out that when you have an inheritance heirarchy, and state is used in both the superclass and subclasses, it can be difficult to remove the inheritance heirarchy and replace it with delegation. Let's look at an example. Here we're modeling bikes:

class Bicycle
def wheel_circumference
Math::PI * (@wheel_diameter + @tire_diameter)
end
end

class FrontSuspensionMountainBike < Bicycle
def off_road_ability
@tire_diameter * TIRE_WIDTH_FACTOR + @front_fork_travel * FRONT_SUSPENSION_FACTOR
end
end

class RigidMountainBike < Bicycle
def off_road_ability
@tire_diameter * TIRE_WIDTH_FACTOR
end
end

class RoadBike < Bicycle
def off_road_ability
raise "You can't take a road bike off-road"
end
end

In this heirarchy the @tire_diameter instance variable is used in both the superclass and subclass. You can imagine that if we were to try to have the Bicycle class delegate to a FrontSuspensionMountainBike object, we'd have to duplicate the @tire_diameter state. This becomes a bit awkward, particularly if @tire_diameter can change - you'd have to ensure that the the @tire_diameter in Bicycle is kept in synch with the one in FrontSuspensionMountainBike. I'd probably decide that it wasn't worth the effort, and keep the inheritance heirarchy.

But what if we wanted to change the type of bike at run-time? Perhaps we want to upgrade a RigidMountainBike (a bike with no suspension) to a FrontSuspensionMountainBike. Using the state pattern would be ideal, but the traditional state pattern uses delegation. With ruby modules we have a different option. Rather than represent the FrontSuspensionMountainBike and RigidMountainBike behaviour as subclasses of Bicycle, we could make them modules and extend Bicycle with the appropriate module for the behviour that we want:

mountain_bike = RigidMountainBike.new
front_suspension_bike = FrontSuspensionMountainBike.new

becomes

mountain_bike = Bicycle.new.extend(RigidMountainBike)
front_suspension_bike = Bicycle.new.extend(FrontSuspensionMountainBike)

class Bicycle
def wheel_circumference
Math::PI * (@wheel_diameter + @tire_width)
end
end

module FrontSuspensionMountainBike
def off_road_ability
@tire_width * TIRE_WIDTH_FACTOR + @front_fork.travel * FRONT_SUSPENSION_FACTOR
end
end

module RigidMountainBike
def off_road_ability
@tire_width * TIRE_WIDTH_FACTOR
end
end


So we could conceivably upgrade our mountain bike at run-time to add a fork with front suspension:

bike = Bicycle.new.extend(RigidMountainBike)
...
bike.add_front_suspension(fork)

module RigidMountainBike...
def add_front_suspension(fork)
@front_fork = fork
extend(FrontSuspensionMountainBike)
end
end

So we now have the ability to change behaviour at run-time, and we haven't introduced any duplication - the state and behaviour is still shared between the Bicycle class and the modules. But there's a problem:

bike.kind_of?(FrontSuspensionMountainBike) => true    
bike.kind_of?(RigidMountainBike) => true

We were able to mix in the FrontSuspensionMountainBike behaviour, but the RigidMountainBike behaviour still exists on the bike object. It turns out that ruby doesn't provide the ability to unmix a module. But all is not lost - there's an open-source library called mixology that does exactly what we want.
require 'mixology'

module RigidMountainBike

def add_front_suspension(fork)
@front_fork = fork
unmix(RigidMountainBike)
mixin(FrontSuspensionMountainBike)
end

end

It provides two methods: unmix (for removing a module from an object) and mixin, for adding a module to an object. Our bug is now fixed:

bike = Bicycle.new.extend(RigidMountainBike)
...
bike.add_front_suspension(fork)

bike.kind_of?(RigidMountainBike) => false

Tuesday, November 18, 2008

Bored with the rhetoric

During election time, I often find myself contemplating argumentative techniques as I watch the politicians do battle for votes. But it's not electioneering that I'm sick of - when you don't have a TV, live in the mountains, and avoid USA Today, you can moderate your intake pretty easily. It's programmer rhetoric that's gotten me down. We're an opinionated bunch, we developers. I'd like to think there's some relationship between an enjoyment of discrete mathematics and a healthy passion for the subtleties of logical argument, but rationality doesn't always reign supreme. Here's a few examples that I've seen over the past year:

Using the phrases "I'm just being pragmatic", or "my solution is the simplest thing that works" to convince someone the merits of an idea.
If you respect the person you are arguing with, and you are both aiming for a pragmatic and simple solution (as we all should be), then these statements are not helping. You're trying to discredit the other person's argument by implying that they are not pragmatic, or their solution is too complex. If the two of you agree that one solution is simpler than the other, then focus on why the simpler solution is adequate.

"But that's not Agile"
I see a repeating pattern in software. Over time, we learn from our mistakes. Out of this learning, we come up with some high level goals (such as the four specified in the Agile manifesto). We then develop some set of practices to help guide people in achieving the goals. Then we follow the practices, refining them a bit to make sure the goals are met. But then, after time, we get caught up in the mechanics of the practices, and forget what the goals are all about. Practices are often much easier to understand. Often when I hear "but that's not Agile", it's to do with a practice. It might be "If a story is more than 3 lines long, we're not being Agile". But the manifesto goal is "Customer collaboration over contract negotiation". So long as we're not sacrificing customer collaboration with our 4th line on our story, then it shouldn't be a problem. And if you think we're hurting our customer collaboration, focus on that in the argument.

The Logical Fallacy
"see, that's why we need continuous integration"
This quote comes from me. I said it. I use programmer rhetoric too :( But I'm trying to get better... Anyway, I was arguing with my project manager about our need for continuous integration. We were developing on macs, and deploying to linux. We had a problem that was only exposed on linux. I had been advocating for a linux machine to run Cruise Control. So I jumped on the chance to further my argument. "See, that's why we need continuous integration". But my project manager called me out on it. He's a smart guy. And he said "So are you telling me that you would have had a test that exposed this problem?". I had to admit that no, I wouldn't have. I'd used a logical fallacy. Yes, continuous integration helps expose bugs. Yes, we had a bug. Both of those statements are logically correct. But it does not follow that continuous integration would have exposed this bug. Shame on me...

As Steven Levitt says in Freakonmics, professionals tend to take advantage of their specialised knowledge to better themselves. He's talking specifically about realtors in one chapter, but programmers aren't above this behaviour. The skills and knowledge that we have gained in order to do our jobs is pretty rare, and we're privileged to be in the position we're in. But all too often I see developers taking advantage of their specialised knowledge during an argument and misleading their opponent by talking about something that the opponent doesn't understand. If you want to win the argument, explain your reasoning in a language that your adversary understands.

And yes, I do understand the irony of using rhetoric to describe my frustration with rhetoric. But I swear it's for good, not evil.