Skip to content

Instantly share code, notes, and snippets.

@kchens
Created April 7, 2015 04:54
Show Gist options
  • Save kchens/4cf2b523ae0dc4566db4 to your computer and use it in GitHub Desktop.
Save kchens/4cf2b523ae0dc4566db4 to your computer and use it in GitHub Desktop.
POODR: Chapter 6 - Acquiring Behavior Through Inheritance
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Starting Class
# 1 -------------------Okay
class Bicycle
attr_reader :size, :tape_color
def initialize(args)
@size = args[:size]
@tape_color = args[:tape_color]
end
# every bike ahs the same defaults for tire and chain size
def spares
{ chain: '10-speed',
tire_size: '23',
tape_color: tape_color }
end
# other methods
end
bike = Bicycle.new(
size: 'M',
tape_color: 'red')
p bike.size
p bike.spares
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Embedding Multiple Types
# 2 -------------------Bad
class Bicycle
attr_reader :style, :size, :tape_color,
:front_shock, :rear_shock
def initialize(args)
@style = args[:style]
@size = args[:size]
@tape_color = args[:tape_color]
@front_shock = args[:front_shock]
@rear_shock = args[:rear_shock]
end
# checking 'style' starts down a slipper slope
# the if statement above shows a 'hidden' type
def spares
if style == :road
{ chain: '10-speed',
tire_size: '23',
tape_color: tape_color }
else
{ chain: '10-speed',
tire_size: '2.1',
rear_shock: rear_shock }
end
end
end
p bike = Bicycle.new(
style: :mountain,
size: 'S',
front_shock: 'Manitou',
rear_shock: 'Fox' )
p bike.spares
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Misapplying Inheritance
# 3 -------------------Bad
class MountainBike < Bicycle
attr_reader :front_shock, :rear_shock
def initialize(args)
@front_shock = args[:front_shock]
@rear_shock = args[:rear_shock]
super(args)
end
def spares
super.merge(rear_shock: rear_shock)
end
end
m_bike = MountainBike.new(
size: 'S',
front_shock: 'Manitou',
rear_shock: 'Tox')
p m_bike.size
# Prints out a mish-mash of mountain bike and bicycle
p m_bike.spares
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Creating an Abstract Superclass
# 4 -------------------Good
class Bicycle
# empty
end
# Pull down the bicycle code into one of the subclasses
# Easier to promote, than to demote behavior
class RoadBike < Bicycle
# Now a subclass of Bicycle.
# Contains all code from the old Bicycle class.
end
class MountainBike < Bicycle
# Still a subclass of Bicycle (which is now empty).
# Code has not changed.
end
road_bike = RoadBike.new(
size: 'M',
tape_color: 'red' )
road_bike.size # 'M'
mountain_bike = MountainBike.new(
size: 'S',
front_shock: 'Manitou',
rear_shock: 'Fox')
mountain_bike.size # NoMethodError: undefined method 'size'
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Promoting Abstract Superclass
# 5 -------------------Good
class Bicycle
attr_reader :size
# now when subclasses call size, the message is delegated to this superclass
def initialize(args={})
@size = args[:size]
end
end
class RoadBike < Bicycle
attr_reader :tape_color
def initialize(args)
@tape_color = args[:tape_color]
super(args)
end
# ...
end
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Separating Abstract from Concrete
# 6 -------------------Good
class RoadBike < Bicycle
# ...
def spares
{ chain: '10-speed',
tire_size: '23',
tape_color: tape_color }
end
end
class MountainBike < Bicycle
# ...
def spares
super.merge({rear_shock: rear_shock})
end
end
mountain_bike.spares # NoMethodError: super: no superclass method'spares
class Bicycle
attr_reader :size, :chain, :tire_size
def initialize(args={})
@size = args[:size]
@chain = args[:chain]
@tire_size = args[:tire_size]
end
end
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Using the Template Method Pattern
# 7 -------------------Good
class Bicycle
attr_reader :size, :chain, :tire_size
def initialize(args={})
@size = args[:size]
@chain = args[:chain] || default_chain
@tire_size = args[:tire_size] || default_tire_size
end
def default_chain # common default
'10-speed'
end
def default_tire_size
# SOMETHING
end
end
class RoadBike < Bicycle
# ...
def default_tire_size # subclass default
'23'
end
end
class MountainBike < Bicycle
# ...
def default_tire_size # subclass default
'2.1'
end
end
road_bike = RoadBike.new(
size: 'M',
tape_color: 'red' )
road_bike.tire_size # '23'
road_bike.chain # '10-speed'
mountain_bike = MountainBike.new(
size: 'S',
front_shock: 'Manitou',
rear_shock: 'Fox' )
mountain_bike.tire_size # '2.1'
road_bike.chain # '10-speed'
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Implementing Every Template Method
# 8 -------------------Good
class RecumbentBike < Bicycle
def default_chain
'9-speed'
end
# forget to implement default_tire_size
end
bent = RecumbentBike.new #NameError: undefined local variable or method
# 'default_tire_size'
class Bicycle
# ...
def default_tire_size
raise NotImplementedError
end
end
bent = RecumbentBike.new # NotImplementedError: NotImplementedError
# Better Implementation----------------------
class Bicycle
# ...
def default_tire_size
raise NotImplementedError
"This #{self.class} cannot respond to:"
end
end
bent = RecumbentBike.new
# NotImplementedError:
# This RecumbentBike cannot respond to: 'default_tire_size'
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Understanding Coupling
# 9 -------------------Good
class RoadBike < Bicycle
# ...
def spares
{ chain: '10-speed',
tire_size: '23',
tape_color: tape_color }
end
end
class MountainBike < Bicycle
# ...
def spares
super.merge({rear_shock: rear_shock})
end
end
class Bicycle
# ...
def spares
{ tire_size: tire_size,
chain: chain }
end
end
# Full Implementation----------------------
class Bicycle
attr_reader :size, :chain, :tire_size
def initialize(args={})
@size = args[:size]
@chain = args[:chain] || default_chain
@tire_size = args[:tire_size] || default_tire_size
end
def spares
{ tire_size: tire_size,
chain: chain }
end
def default_chain
'10-speed'
end
def default_tire_size
raise NotImplementedError
end
end
class RoadBike < Bicycle
attr_reader :tape_color
def initialize(args)
@tape_color = args[:tape_color]
super(args)
end
def spares
super.merge({ tape_color: tape_color} )
end
def default_tire_size
'23'
end
end
class MountainBike < Bicycle
attr_reader :front_shock, :rear_shock
def initialize(args)
@front_shock = args[:front_shock]
@rear_shock = args[:rear_shock]
super(args)
end
def spares
super.merge({rear_shock: rear_shock})
end
def default_tire_size
'2.1'
end
end
class RecumbentBike < Bicycle
attr_reader :flag
def initialize(args)
@flag = args[:flag]
end
def spares
super.merge({flag: flag})
end
def default_chain
'9-speed'
end
def default_tire_size
'28'
end
end
bent = RecumbentBike.new(flag: 'tall and orange')
p bent.spares
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Decoupling Subclasses Using Hook Messages
# 10 -------------------Best
class Bicycle
def initialize(args={})
@size = args[:size]
@chain = args[:chain] || default_chain
@tire_size = args[:tire_chain] || default_tire_size
post_initialize(args) # Bicycle both sends
end
def post_initialize(args) # and implements this
nil
end
# ...
end
class RoadBike < Bicycle
# Remove #initialize altogether
def post_initialize(args) # RoadBike can
@tape_color = args[:tape_color] # optionally
end # override it
# ...
end
# 11 -------------------Best
class Bicycle
# ...
def spares
{ tire_size: tire_size,
chain: chain}.merge(local_spares)
end
# hook for subclasses to override
def local_spares
{}
end
end
class RoadBike < Bicycle
# ...
def local_spares
{ tape_color: tape_color }
end
end
# 12 -------------------Best
class Bicycle
attr_reader :size, :chain, :tire_size
def initialize(args={})
@size = args[:size]
@chain = args[:chain] || default_chain
@tire_size = args[:tire_size] || default_tire_size
post_initialize(args)
end
def spares
{ tire_size: tire_size,
chain: chain}.merge(local_spares)
end
def default_tire_size
raise NotImplementedError
end
# subclasses may override
def post_initialize(args)
nil
end
def local_spares
{}
end
def default_chain
'10-speed'
end
end
class RoadBike < Bicycle
attr_reader :tape_color
def post_initialize(args)
@tape_color = args[:tape_color]
end
def local_spares
{ tape_color: tape_color }
end
def default_tire_size
'23'
end
end
class MountainBike < Bicycle
attr_reader :front_shock, :rear_shock
def post_initialize(args)
@front_shock = args[:front_shock]
@rear_shock = args[:rear_shock]
end
def local_spares
{ rear_shock: rear_shock }
end
def default_tire_size
'2.1'
end
end
class RecumbentBike < Bicycle
attr_reader :flag
def post_initialize(args)
@flag = args[:flag]
end
def local_spares
{ flag: flag }
end
def default_chain
'9-speed'
end
def default_tire_size
'28'
end
end
bent = RecumbentBike.new(flag: 'tall and orange')
p bent.spares
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment