The OCP kata
Tuesday, January 12th, 2010Read the first chapter of the Patterns book, it’s all there, where it says “favor composition over inheritance”,
said Jacopo. We were chatting about the Open/Closed Principle, and how I read about it in Meyer in 1991, yet it didn’t “click” for me back then.
Now I see how the OCP is key to writing code that can be changed easily, which is the chief technical goal of an agile team. I wondered, is there a way to teach and learn the OCP? Is there a kata to learn OCP?
It’s unfortunate that most common coding katas result in “single-object-oriented programming”. The famous bowling score example by Robert Martin, for instance, is usually solved by creating *one* object. This might be fine for learning how to write simple code. It’s not so good for learning how to do object-oriented design.
So I invented this little exercise to practice and learn OCP.
Take any coding problem. The bowling score, the string evaluator, the supermarket checkout, you name it. Then follow these instructions.
0. Write the first failing test. Then write a factory that returns an object, or an aggregate of objects, that make the test pass.
The factory should be limited to creating objects and linking them together. No conditionals allowed.
1. Write the next failing test.
2. Can you make it pass by changing the factory and/or creating a new class and nothing else? If yes, great! Go back to 1. If not, refactor until you can.
The refactoring should bring the system to a state where it’s possible to implement the next test just by changing the aggregate of objects that is returned by the factory. Be careful not to implement new functionality; the current test should still fail.
For instance, take the bowling score problem. The first test is
@Test public void gutterGame() throws Exception { BowlingGame game = new BowlingGameFactory().create(); for (int i=0; i<20; i++) { game.roll(0); } assertEquals(0, game.score()); }
The code to make this pass is
class BowlingGameFactory { public BowlingGame create() { return new BowlingGame(); } } class BowlingGame { public void roll(int n) {} public int score() { return 0; } }
Nothing strange here. Now the second test is
@Test public void allOnesGame() throws Exception { BowlingGame game = new BowlingGameFactory().create(); for (int i=0; i<20; i++) { game.roll(1); } assertEquals(20, game.score()); }
The simplest code that makes both tests pass would be to change BowlingGame to accumulate rolls in a variable. But our rules stop us from doing that; we must find a way to implement the new functionality with a new object. I think about it for a few minutes, and all I can think of is to delegate to another object the accumulation of rolls. I will call this role “Rolls”. Cool! This forces me to invent a new design idea. But I must be careful not to add new functionality, so I will just write a Rolls object that always returns 0.
interface Rolls { void add(int n); int sum(); } class BowlingGame { private final Rolls rolls; public BowlingGame(Rolls rolls) { this.rolls = rolls; } public void roll(int n) { rolls.add(n); } public int score() { return rolls.sum(); } } class BowlingGameFactory { public BowlingGame create() { Rolls zero = new Rolls() { public void add(int n) {} public int sum() { return 0; } }; return new BowlingGame(zero); } }
This passes the first test, and still fails the second. In order to pass the second test, all I have to do is provide a real implementation of Rolls and change the factory.
class Accumulator implements Rolls { void add(int n) { ... } int sum() { ... } } class BowlingGameFactory { public BowlingGame create() { return new BowlingGame(new Accumulator()); } }
And so on. The point here is to think about how to
- compose functionality out of existing objects, and
- avoid reworking existing code.
Feedback?
Updates
24/12/2013 Chris F Carroll‘s Gilded Mall Kata is a new exercise that can be done with the OCP kata rules. Thanks Chris!
29/05/2014 There is now a repository with a few prepared exercises! I presented this at XP2014.