Created
August 25, 2015 16:55
-
-
Save blake41/ba6188456a217ddeca77 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
# All bicycles are road bikes | |
# A Mechanic is going to need to know what spare parts to take | |
# for a bike on a trip in case it breaks down | |
# tape color is the only dynamic component of spares | |
############## Page 107 ############## | |
class Bicycle | |
attr_reader :size, :tape_color | |
def initialize(args) | |
@size = args[:size] | |
@tape_color = args[:tape_color] | |
end | |
# every bike has the same defaults for | |
# tire and chain size | |
def spares | |
{ chain: '10-speed', | |
tire_size: '23', | |
tape_color: tape_color} | |
end | |
# Many other methods... | |
end | |
bike = Bicycle.new( | |
size: 'M', | |
tape_color: 'red' ) | |
bike.size # -> 'M' | |
bike.spares | |
# -> {:tire_size => "23", | |
# :chain => "10-speed", | |
# :tape_color => "red"} | |
# What if we want to start going on trips with both road and | |
# mountain bikes | |
############## Page 110 ############## | |
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 slippery slope | |
def spares | |
if style == :road | |
{ chain: '10-speed', | |
tire_size: '23', # milimeters | |
tape_color: tape_color } | |
else | |
{ chain: '10-speed', | |
tire_size: '2.1', # inches | |
rear_shock: rear_shock } | |
end | |
end | |
end | |
bike = Bicycle.new( | |
style: :mountain, | |
size: 'S', | |
front_shock: 'Manitou', | |
rear_shock: 'Fox') | |
bike.spares | |
# -> {:tire_size => "2.1", | |
# :chain => "10-speed", | |
# :rear_shock => 'Fox'} | |
# PROBLEMS | |
# deciding what to do based on types | |
# if we add a new type, we need to change the if statement | |
# if we pass in a style but have a typo, we'll get a default bike (the else block) | |
# we duplicate the strings on both sides of the if else | |
# we can't be sure we will have values for all the attr readers | |
# we could get back nils, so code that uses a bicycle might have to do | |
# a nil check or check the style of the bike (violating ask don't tell) | |
# inheritance | |
# why do we use inheritance? | |
# The style variable effectively divides instances of Bicycle into | |
# two different kinds of things. These two things share a great deal | |
# of behavior but differ along the style dimension. Some of Bicycle’s | |
# behavior applies to all bicycles, some only to road bikes, and | |
# some only to mountain bikes. This single class contains several | |
# different, but related, types. | |
# This is the exact problem that inheritance solves; that of highly | |
# related types that share common behavior but differ along some dimension. | |
############## Page 115 ############## | |
# let's just create a mountain bike that inherits from bike | |
# WHAT DOES SUPER DO | |
# METHOD LOOKUP CHAIN | |
class Bicycle | |
attr_reader :size, :tape_color | |
def initialize(args) | |
@size = args[:size] | |
@tape_color = args[:tape_color] | |
end | |
# every bike has the same defaults for | |
# tire and chain size | |
def spares | |
{ chain: '10-speed', | |
tire_size: '23', | |
tape_color: tape_color} | |
end | |
# Many other methods... | |
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 | |
end | |
############## Page 115 ############## | |
mountain_bike = MountainBike.new( | |
size: 'S', | |
front_shock: 'Manitou', | |
rear_shock: 'Fox') | |
mountain_bike.size # -> 'S' | |
mountain_bike.spares | |
# -> {:tire_size => "23", <- wrong! | |
# :chain => "10-speed", | |
# :tape_color => nil, <- not applicable | |
# :front_shock => 'Manitou', | |
# :rear_shock => "Fox"} | |
############## Page ?? ############## | |
# So let's push everything into the subclasses and only promote shared behavior | |
# This first iteration is going to cause problems | |
class Bicycle | |
# This class is now empty. | |
# All code has been moved to RoadBike. | |
end | |
class RoadBike < Bicycle | |
attr_reader :size, :tape_color | |
def initialize(args) | |
@size = args[:size] | |
@tape_color = args[:tape_color] | |
end | |
def spares | |
{ chain: '10-speed', | |
tire_size: '23', | |
tape_color: tape_color} | |
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 | |
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' | |
############## Page 121 ############## | |
# let's promote shared behavior | |
# | |
class Bicycle | |
attr_reader :size # <- promoted from RoadBike | |
def initialize(args={}) | |
@size = args[:size] # <- promoted from RoadBike | |
end | |
end | |
class RoadBike < Bicycle | |
attr_reader :tape_color | |
def initialize(args) | |
@tape_color = args[:tape_color] | |
super(args) # <- RoadBike now MUST send 'super' | |
end | |
# ... | |
end | |
############## Page 122 ############## | |
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 # -> 'S' | |
############## Page ??? ############## | |
# size now works, but what about spares? | |
class Bicycle | |
attr_reader :size | |
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 | |
def spares | |
{ chain: '10-speed', | |
tire_size: '23', | |
tape_color: tape_color} | |
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 | |
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 # -> 'S' | |
mountain_bike.spares | |
# NoMethodError: super: no superclass method `spares' | |
############## Page 123 ############## | |
class RoadBike < Bicycle | |
# ... | |
def spares | |
{ chain: '10-speed', | |
tire_size: '23', | |
tape_color: tape_color} | |
end | |
end | |
############## Page 125 ############## | |
# let's make both bike types have different size chains and tires | |
class Bicycle | |
attr_reader :size, :chain, :tire_size | |
def initialize(args={}) | |
@size = args[:size] | |
@chain = args[:chain] | |
@tire_size = args[:tire_size] | |
end | |
# ... | |
end | |
############## Page 126 ############## | |
# let's implement a subclass default | |
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 | |
end | |
# TALK ABOUT THE LOOKUP CHAIN | |
# ANCESTORS | |
class RoadBike < Bicycle | |
# override the default in the subclass | |
# ... | |
def default_tire_size # <- subclass default | |
'23mm' | |
end | |
end | |
class MountainBike < Bicycle | |
# ... | |
def default_tire_size # <- subclass default | |
'2.1 inch' | |
end | |
end | |
############## Page 126 ############## | |
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' | |
mountain_bike.chain # => "10-speed" | |
############## Page 127 ############## | |
# let's add a new type of bike | |
class RecumbentBike < Bicycle | |
def default_chain | |
'9-speed' | |
end | |
end | |
bent = RecumbentBike.new | |
# NameError: undefined local variable or method | |
# `default_tire_size' | |
############## Page ??? ############## | |
# This line of code is a time bomb | |
@tire_size = args[:tire_size] || default_tire_size | |
############## Page 128 ############## | |
# superclass should let us know that all subclasses MUST implement | |
class Bicycle | |
#... | |
def default_tire_size | |
raise NotImplementedError | |
end | |
end | |
############## Page 131 ############## | |
# good 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 NotImplememtedError | |
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 | |
############## Page ??? ############## | |
##### Results for the above | |
road_bike = RoadBike.new( | |
size: 'M', | |
tape_color: 'red' ) | |
road_bike.spares | |
# -> {:tire_size => "23", | |
# :chain => "10-speed", | |
# :tape_color => "red"} | |
mountain_bike = MountainBike.new( | |
size: 'S', | |
front_shock: 'Manitou', | |
rear_shock: 'Fox') | |
mountain_bike.spares | |
# -> {:tire_size => "2.1", | |
# :chain => "10-speed", | |
# :rear_shock => "Fox"} | |
############## Page 133 ############## | |
# if we add a new class, but forget to send super! | |
# new programmer, doesn't know codebase | |
class RecumbentBike < Bicycle | |
attr_reader :flag | |
def initialize(args) | |
@flag = args[:flag] # forgot to send 'super' | |
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') | |
bent.spares | |
# -> {:tire_size => nil, <- didn't get initialized | |
# :chain => nil, | |
# :flag => "tall and orange"} | |
############## Page ??? ############## | |
# lets add post initialize hook that subclasses should implement | |
# maybe raise an error if subclasses don't? | |
# same with local spares | |
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 post_initialize(args) | |
nil | |
end | |
def spares | |
{ tire_size: tire_size, | |
chain: chain}.merge(local_spares) | |
end | |
def local_spares | |
{} | |
end | |
def default_chain | |
'10-speed' | |
end | |
def default_tire_size | |
raise NotImplementedError | |
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 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') | |
bent.spares | |
# -> {:tire_size => "28", | |
# :chain => "10-speed", | |
# :flag => "tall and orange"} | |
road_bike = RoadBike.new( | |
size: 'M', | |
tire_size: 25, | |
tape_color: 'red' ) | |
road_bike.spares | |
# -> {:tire_size => 25, | |
# :chain => "10-speed", | |
# :tape_color => "red"} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment