-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathduck_typing.rb
181 lines (142 loc) · 3.57 KB
/
duck_typing.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# Reducing Costs with Duck Typing
#
# Overlooking the Duck
class Trip
attr_reader :bicycles, :customers, :vehicle
# this 'mechanic' argument could be of any class
def prepare(mechanic)
mechanic.prepare_bicycles(bicycles)
end
end
# if you happen to pass an instance of *this* class,
# it works
class Mechanic
def prepare_bicycles(bicycles)
bicycles.each {|bicycle| prepare_bicycle(bicycle)}
end
def prepare_bicycle(bicycle)
#...
end
end
######################################
# Imagine that requirements change. In addition to a mechanic, trip preparation
# now involves a trip coordinator and a driver. Following the established pattern of the
# code, you create new TripCoordinator and Driver classes and give them the
# behavior for which they are responsible.
# Trip preparation becomes more complicated
class Trip
attr_reader :bicycles, :customers, :vehicle
def prepare(preparers)
preparers.each {|preparer|
case preparer
when Mechanic
preparer.prepare_bicycles(bicycles)
when TripCoordinator
preparer.buy_food(customers)
when Driver
preparer.gas_up(vehicle)
preparer.fill_water_tank(vehicle)
end
}
end
end
# when you introduce TripCoordinator and Driver
class TripCoordinator
def buy_food(customers)
# ...
end
end
class Driver
def gas_up(vehicle)
#...
end
def fill_water_tank(vehicle)
#...
end
end
######################################
# Finding the duck
class Trip
attr_reader :bicycles, :customers, :vehicle
def prepare(preparers)
preparers.each {|preparer| preparer.prepare_trip(self) }
end
end
class TripCoordinator
def buy_food(customers)
# ...
end
def prepare_trip(trip)
buy_food(trip.customers)
end
end
class Driver
def gas_up(vehicle)
#...
end
def fill_water_tank(vehicle)
#...
end
def prepare_trip(trip)
vehicle = trip.vehicle
gas_up(vehicle)
fill_water_tank(vehicle)
end
end
class Mechanic
def prepare_trip(trip)
prepare_bicycles(trip.bicycles)
end
def prepare_bicycles(bicycles)
bicycles.each {|bicycle| prepare_bicycle(bicycle)}
end
def prepare_bicycle(bicycle)
#...
end
end
######################################
# Writing Code That Relies on Ducks
# Recognizing Hidden Ducks
# You can replace the following with ducks:
# • Case statements that switch on class
# • kind_of? and is_a?
# • responds_to?
# Case Statements That Switch on Class
class Trip
attr_reader :bicycles, :customers, :vehicle
def prepare(preparers)
preparers.each {|preparer|
case preparer
when Mechanic
preparer.prepare_bicycles(bicycles)
when TripCoordinator
preparer.buy_food(customers)
when Driver
preparer.gas_up(vehicle)
preparer.fill_water_tank(vehicle)
end
}
end
end
# kind_of? and is_a?
if preparer.kind_of?(Mechanic)
preparer.prepare_bicycles(bicycle)
elsif preparer.kind_of?(TripCoordinator)
preparer.buy_food(customers)
elsif preparer.kind_of?(Driver)
preparer.gas_up(vehicle)
preparer.fill_water_tank(vehicle)
end
# responds_to?
if preparer.responds_to?(:prepare_bicycles)
preparer.prepare_bicycles(bicycle)
elsif preparer.responds_to?(:buy_food)
preparer.buy_food(customers)
elsif preparer.responds_to?(:gas_up)
preparer.gas_up(vehicle)
preparer.fill_water_tank(vehicle)
end
# In each case the code is effectively saying
# "I know who you are and because of that I know what you do.”
# This knowledge exposes a lack of trust in collaborating objects and acts as a
# millstone around your object’s neck.