Created
March 4, 2014 21:19
-
-
Save Najaf/9355906 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# We prefer composition over inheritance because inheritance couples type to | |
# implementation. | |
# | |
# Take a look at this example using inheritance: | |
class Duck | |
def initialize | |
@wings = Wings.new | |
end | |
def fly | |
@wings.flap! | |
end | |
def quack | |
puts "quack" | |
end | |
end | |
class LoudDuck < Duck | |
def quack | |
puts "QUACK" | |
end | |
end | |
# Spot the difference between those two. The class (type) is different, as is | |
# the implementation of the quack method. This class definition is saying that | |
# there exists another type of duck LoudDuck, and they're special in that they | |
# quack different to regular ducks i.e. the type and the implementation of | |
# quack are coupled. | |
# | |
# This is fine if the only axes along which ducks can be different to each | |
# other is the way they quack. It's often the case however that you want | |
# objects that are still ostensibly ducks, but behave in any combination of | |
# ways at runtime. | |
# | |
# Consider this example: | |
class RocketDuck < Duck | |
def fly | |
RocketEngine.new.ignite! | |
super | |
end | |
end | |
# This time the axes along which the ducks are different is the way they fly, | |
# in this case since the duck is a rocket duck, it flies by igniting rockets | |
# and flapping it's wings. | |
# | |
# All this class definition is saying that there are ducks called RocketDucks, | |
# and what's special about them is the implementation of fly is different to | |
# that of regular ducks. The type is coupled to implementation again. | |
# | |
# But wait, what if you want a duck that is both loud and flies with rockets? | |
# Well you're going to need another type and a bit of duplicate code for that | |
# if you're going to continue to use inheritance: | |
class LoudRocketDuck < Duck | |
def fly | |
RocketEngine.new.ignite! | |
super | |
end | |
def quack | |
puts "QUACK" | |
end | |
end | |
# Now what if you have a silent duck too? | |
class SilentDuck < Duck | |
def quack | |
# no-op | |
end | |
end | |
# And then when you need a silent RocketDuck you'll need a version for that too: | |
class SilentRocketDuck < Duck | |
def fly | |
RocketEngine.new.ignite! | |
super | |
end | |
def quack | |
# no-op | |
end | |
end | |
# It obviously gets worse if you have other methods that change between classes | |
# that you want to re-use, or if you have multiple methods in a given class | |
# that differ in interesting ways, some re-usable and some not. | |
# | |
# You can use all sorts of gymnastics to wrestle with this (e.g. pull commonly | |
# used functionality into abstract classes) but ultimately you're just | |
# attempting to couple type and implementation "better" than you were before. | |
# It's a bit like trying to build something out of lego except sometimes you | |
# arbitrarily superglue certain pieces together because it feels right at the | |
# time. | |
# | |
# In all of these cases we're using inheritance to group implementations of | |
# methods in the various combinations they come in. My combinatorics thinking | |
# is a little weak, but the number of classes probably expands to something | |
# like the number of variations on each axes multiplied together, minus | |
# something. | |
# | |
# Composition helps us because instead of having to define a new type for every | |
# combination of behaviour, we can build up the desired behaviour at runtime. | |
# It works because we no longer couple the type of the composing class to the | |
# concrete implementations of its methods. | |
# | |
# Pretend all those classes above don't exist and let's start from scratch: | |
class Duck | |
attr_accessor :flight_system, :quacker, :wings | |
def initialize | |
@wings = Wings.new | |
end | |
def flap_wings | |
@wings.flap! | |
end | |
def fly | |
self.flight_system.call(self) | |
end | |
def quack | |
self.quacker.call(self) | |
end | |
end | |
class DefaultFlightSystem | |
def call(duck) | |
duck.flap_wings | |
end | |
end | |
class RocketFlightSystem | |
def call(duck) | |
RocketEngine.new.ignite! | |
duck.flap_wings | |
end | |
end | |
class LoudQuacker | |
def call(duck) | |
puts "QUACK" | |
end | |
end | |
# etc etc. Now we've isolated the parts that change in our composed objects and | |
# don't need to create a class for every possible grouping of them. *This* is | |
# why we like composition over inheritance. | |
# | |
# Now when we want a LoudRocket duck, we just wire one up in the client code: | |
loud_rocket_duck = Duck.new.tap do |d| | |
d.flight_system = RocketFlightSystem.new | |
d.quacker = LoudQuacker.new | |
end | |
# Note that you may not like the API here. That isn't really the point. The | |
# point is that now the implementation is decoupled from the type. How the | |
# implementation is now hooked up with our object is pretty much arbitrary. | |
# | |
# You can doll it up a little by say taking a hash in the initializer that | |
# configures the various bits or perhaps take symbols that key into a hash of | |
# possible classes that get initialized for you. You can switch `call` out for | |
# something a little more specific to your domain, but as you probably know, | |
# using `call` allows you to pass in any callable like a lambda or whatever. | |
# | |
# You could also use decorators e.g. wrap the ducks so you'd do something like: | |
loud_rocket_duck = RocketFlightDecorator.new(LoudQuackDecorator.new(Duck.new)) | |
# With the RocketFlightDecorator instance responding to the `fly` method and | |
# delegating every other message to whatever you passed into the initializer. | |
# In this case LoudQuackDecorator instance would handle the quack method and | |
# delegate everything else down to the Duck instance, so say when the | |
# RocketFlightDecorator calls `flap_wings` on the LoudQuackDecorator, the | |
# message eventually reaches the Duck instance. I'm not going to type the | |
# implementation for that out. | |
# | |
# You could achieve effectively the same result using modules: | |
class Duck | |
def initialize | |
@wings = Wings.new | |
end | |
def fly | |
@wings.flap! | |
end | |
end | |
module FliesWithRockets | |
def fly | |
RocketEngine.new.ignite! | |
super | |
end | |
end | |
module QuacksLoudly | |
def quack | |
puts "QUACK" | |
end | |
end | |
loud_rocket_duck = Duck.new.tap do |d| | |
d.extend FliesWithRockets, QuacksLoudly | |
end | |
# The only difference is that this time, instead of wiring up the objects | |
# yourself, you're using Ruby's built-in call chain to handle the delagation | |
# for you. If you really really have to make this look like traditional Java | |
# composition, you can picture the module as the object that handles the | |
# implementation and gets composed by the class it gets mixed into. | |
# | |
# Using delegators, decorators, presenters, whatever you want to call them, has | |
# the benefit over modules of not coupling the *object* to the implementation, | |
# but such an advantage has nothing to do with the original argument of | |
# composition being better than inheritance. An object not being coupled to | |
# it's implementation didn't really make sense in classical OO languages. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment