Skip to content

Instantly share code, notes, and snippets.

@Najaf
Created March 4, 2014 21:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Najaf/9355906 to your computer and use it in GitHub Desktop.
Save Najaf/9355906 to your computer and use it in GitHub Desktop.
# 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