- Why favor composition over inheritance?
- When to choose inheritance?
-
Composition - "has a" or "uses a" relationship
Car
has anEngine
Person
has aName
-
Inheritance - "is a" relationship
Car
is aVehicle
Person
is aMammal
- Does
Type B
wants to expose the complete interface (all public methods) ofType A
, such thatType B
can be used anywhereType A
is expected?
A Biplane
will expose the complete interface of an Airplane
. So, it makes perfect sense to derive it from Airplane
.
- Does
Type B
only wants some of the behavior (public methods) ofType A
?
A Bird
may only need the fly
behavior of an Airplane
. In this case, it makes sense to extract it out and make it a member of both classes.
If S
is a subtype of T
, then objects of type T
may be replaced with objects of type S
without altering
any of the desirable properties of that program (correctness, task performed, etc.)
A Square
class that derives from a Rectangle
class, assuming getter and setter methods exist for both width
and height
. The Square
class always assumes that the width
is equal with the height
. If a Square
object is used in a context where a Rectangle
is expected, unexpected behavior may occur because the dimensions of a Square
cannot (or rather should not) be modified independently.
class Rectangle
attr_reader :width, :height
def initialize width, height
@width = width
@height = height
end
def width= value
@width = value
end
def height= value
@height = value
end
end
class Square < Rectangle
def width= value
@width = value
@height = value
end
def height= value
@height = value
@width = value
end
end
class Person
def walkNorth
# walks north
end
def walkSouth
# walks south
end
end
class Prisoner < Person
def walkNorth
# not allowed
end
def walkSouth
# not allowed
end
end
Prisoner
is not allowed to walk in any direction, but the contract of the Person
class states that a Person
can.
This strongly suggests that inheritance should never be used when the sub-class restricts the freedom implicit in the base class, but should only be used when the sub-class adds extra detail to the concept represented by the base class as in Monkey
is an Animal
.
Inheritance
class Vehicle
def run
# Run 40km/h
end
end
class Car < Vehicle
end
myCar = Car.new
myCar.run
Composition
class Engine
def start
# Run 40km/h
end
end
class Car
def initialize engine
@engine = engine
end
def run
@engine.start
end
end
You can change the behavior of a class on run-time using composition and dependency injection. This will be very much useful when doing unit tests.
Inheritance
class PhysicsObject
def update
# update physics
end
end
# Enemy is a PhysicsObject
class Enemy < PhysicsObject
def initialize
@health = 100
end
end
Composition
class PhysicsEngine
def update
# update physics
end
end
# Enemy uses a PhysicsEngine
class Enemy
def initialize
@physicsEngine = PhysicsEngine.new
@health = 100
end
def update_physics
@physicsEngine.update
end
end
- Inheritance makes sense? Yes.
- Composition makes sense? Yes.
- What to choose? WTF.
- Two blocks conected by an energy chain.
- Single health bar.
- Blocks can move separately and have their own Physics.
class Enemy
def initialize
@health = 100
@physicsA = PhysicsEngine.new
@physicsB = PhysicsEngine.new
end
def update_physics
@physicsA.update
@physicsB.update
end
end
#
# name :string
# email :string
# password :string
# industry :string
# type :string
#
class User < ActiveRecord::Base
end
class Business < User
end
name
,email
, andpassword
are common betweenUser
andBusiness
models.industry
attribute is forBusiness
instances only.type
is used by Rails to evaluate in which model the record belongs.
- Normal users will have
nil
industry
column. User
objects will have anindustry
andindustry=
methods.
#
# name :string
# email :string
# password :string
# account_holder_id :integer
# account_holder_type :string
#
class Account < ActiveRecord::Base
belongs_to :account_holder, :polymorhpic => true
end
class User < ActiveRecord::Base
has_one :account, :as => :account_holder
end
#
# industry :string
#
class Business < ActiveRecord::Base
has_one :account, :as => :account_holder
end
class User < ActiveRecord::Base
has_one :account, :as => :account_holder
# Delegate
delegate :name, :to => :account
# Wrapper method
def email
self.account.email
end
end
user = User.first
# Directly access using the `Account` object
user.account.password
- Why favor Composition over Inheritance?
- Composition gives more flexibility on a class.
- You can change the behavior of a class during run-time using composition and dependency injection.
- Single Table Inheritance in Rails have weird side-effects (on some situations).
- When to choose Inheritance?
- A class wants to use/expose all public methods of another class.
- A sub-class doesn't change the behavior and definitions of the super class.
- If inherting will pass the Liskov Substitution Principle.
- Inheritance is NOT bad and Composition is not always the correct approach.
- Inheritance is a tighter relationship compared to Composition, so think twice before using it.
- http://en.wikipedia.org/wiki/Liskov_substitution_principle
- http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- http://en.wikipedia.org/wiki/Composition_over_inheritance
- http://en.wikipedia.org/wiki/Circle-ellipse_problem
- http://joostdevblog.blogspot.com/2014/07/why-composition-is-often-better-than.html