I became inspired to write this blog post after watching a great @sandimetz talk which she gave at RailsConf 2015. Her subject was primarily the Null Object Pattern, but she extends the fundamental principle - that objects, not conditionals nor excruciatingly tailored classes, should be used to encapsulate your system’s behaviors - to the practical aspects of injecting dependencies required by your domain’s fundamental objects. Let’s take a look at what I mean by using a real-world example: my recently acquired (and already banged up - I’ve had two flat tires already!) fixed gear (a.k.a “fixie”) bike. I want you to start asking yourself: if you were given the task of modeling in Ruby a fixed-gear bicycle alongside a “normal” or freewheel bicycle, what tools would you reach for - inheritance, composition, or otherwise?
Let’s throw down a little code to start bringing our bicycle domain to life:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Cool - now we can instantiate a new
Bicycle with our frame size and color preferences. Moreover, we have a sensible default (at least for urban NYC bicycling) for our drivetrain set to single-speed and freewheel. (Note that ‘freewheel’ is distinct from fixed-gear. On a fixie, for example, it is not possible for the chain to disengage from the crank arms, i.e. the motion of the rider’s pedals. On a freewheel bicycle, you can stop the motion of the pedals beneath you and the drivetrain will continue turning, allowing for what we call ‘coasting’.)
This is all well and good - until you find yourself moving to Brooklyn. In Brooklyn, fixed gear bikes are more popular, and it’s probably got something to do with a hipster resurgence.
So now let’s use our knowledge of OO and Ruby to model our fixed gear bicycle.
Reach for inheritance first…?
Here is a perfectly viable implementation of our fixie bicycle variant:
1 2 3 4 5
Easy! All we did was inherit from
Bicycle, and modify the
#freewheel method to instantiate a non-freewheel single-speed bicycle.
Now let’s say, however, that your roommate would prefer a multi-speed bicycle. Once again using inheritance we may write some code like so:
1 2 3 4 5
And once again, this has solved our problem. But, while effective, this solution raises a number of concerns. Mainly, we already have two distinct classes in order to account for two slight variations in
Bicycle types. What do you imagine will happen to our system as the number of different variations grows? Put differently:
- Do we really need distinct, unique classes to model a single variation in the characteristics of our bicycle?
- Does the organization of our code and the patterns therein easily communicate the distinctions we’re attempting to convey?
- Are we satisfied with the idea that, should our
Bicycles change in other aspects in the future, we’ll continue to open new classes?
Where inheritance breaks down (pun intended)
To focus on the last concern that we just raised, let’s say you’ve accepted a job as a delivery boy for a local Chinese restaurant. Most of those guys, since they’re riding for hours a day, enhance their drivetrain with an electric motor which looks something like this:
You’ve been asked to allow your system to model this new
Bicycle variant. Perhaps you could reach for inheritance once again and end up with something like this:
1 2 3 4 5
This may meet the needs of a relatively basic system, but let’s say your boss asks you to run a report…
Boss: “Hey you, new gal! Get me a breakdown of all the bicycles in New York City with an approximation of their top speeds. We are examining the relationship between bicycle accidents and their drivetrain mechanism - we’ve been hearing a lot lately about these electric-motor-augmented bicycles getting into accidents at faster speeds than non-electric bicycles.”
Since your boss seems to be asking for data concerning the relationship between bicycles’ drivetrain mechanism and their approximate ‘top speed’, you set out to run the report with some code like this:
Now, you need to re-open all of your classes and implement
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Gheesh, that was kind of a lot of work - we had to open 3 different classes to find a home for our top speed approximations. You can see that our pattern - which is built on top of inheritance - could certainly become unwieldy and difficult to maintain as our system grows.
A better pattern: inject your dependencies!
I think this pattern really speaks for itself, so I’ll let the code do most of the talking here. Instead of inheritance, if we reached for dependency injection our system may have turned our more like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
Now we can easily instantiate our various
Bicycle types and get
#top_speed data from them:
1 2 3 4 5 6 7 8 9
“Isolate what is different”
By isolating in our mind what was different among our bicycle variations, we were able to extract it out into its own
Drivetrain dependency. The real benefit of doing this is that we can inject this dependency into our
Bicycle instances as we need! No more sublcassing
Bicycle endlessly as variation after variation of bike requires modeling in our system. You can envision this pattern of dependency injection coming in handy as your system grows and different attributes of
Bicycle start to vary. Have you seen the foldable, transportable frame style of bikes?
Using dependency injection, we can account for this variable attribute pretty succinctly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
And here is our foldable
1 2 3 4
Watching Sandi’s talk (and writing up this post) have certainly changed my opinion on using inheritance versus injecting dependencies into my domain model. I was inspired to write this post by Sandi and the joy I’ve been getting from riding fixie around Brooklyn for the past couple of months.
I hope you’ve found this blog post interesting and educational. Please let me know in the comments below!