Skip to content

Instantly share code, notes, and snippets.

@blake41
Created August 25, 2015 16:55
Show Gist options
  • Save blake41/ba6188456a217ddeca77 to your computer and use it in GitHub Desktop.
Save blake41/ba6188456a217ddeca77 to your computer and use it in GitHub Desktop.
# 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