Two code dojos ago, we tried using three Object Calisthenics rules (aka “Extreme OO” rules) in our kata exercise. The “Object Calisthenics” essay was published in The Thoughtworks Anthology. Since then, I have checked out the anthology from the YD library. After reading the original essay, I decided to apply these rules to the refactoring that I was doing.
The rule that stuck with me the most this time around was:
8. Use first-class collections. In other words, any class that contains a collection should contain no other member variables. The idea is an extension of primitive obsession. If you need a class that’s a subsumes the collection, then write it that way.
I had a class that I was refactoring (mostly for clarity) that among other things held a cache of data. For simplicity sake, let’s say the cache was held in a
Map<Foo, Bar>. The class then went on to get, put, iterate, and remove from that map.
So, I applied Rule #8 and created a
FooBarLookup class that encapsulated the
Map<Foo, Bar>. I then had the freedom to only add the methods to it that I needed like
Bar get(foo) and
void add(Foo, Bar).
The most important thing that I realized is that I could abstract more powerful methods. For example, when I was done with a given
Bar, I removed it from the
FooBarLookup. In this
Map, I had many
Foo‘s mapping to the same
Bar. So, previously, I had wrote code that iterated through the
Map.entrySet() and removed entries that had matching
Bar‘s. Now, I could now hide this iteration in a new method on
Big whoop, you say.
Well, one nice thing about this encapsulation is if I decided later that I needed to speed up this new remove(Bar) method, I could maybe add a
Multimap reverse lookup to feed the keys to remove into the main
Map. Then, I can manage the bidirectional map setup with the same
add(Foo, Bar) method that I had defined before.
FooBarLookup class also gave me the ability to write some high-order-function-esque methods that map or filter the
Bar‘s in the Lookup. Happily, all of this iteration noise is hidden inside the encapsulated collection, and away from my business logic.