Created
April 7, 2015 04:54
-
-
Save kchens/4cf2b523ae0dc4566db4 to your computer and use it in GitHub Desktop.
POODR: Chapter 6 - Acquiring Behavior Through Inheritance
This file contains hidden or 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
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # | |
# 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