-
Notifications
You must be signed in to change notification settings - Fork 0
/
chapter_3.rb
181 lines (146 loc) · 4.02 KB
/
chapter_3.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
# INJECTING DEPENDENCIES
# Bad
class Gear
attr_deader :chainring, :cog, :rim, :tire
def initialize(chainring, cog, rim, tire)
@chainring = chainring
@cog = cog
@rim = rim
@tire = tire
end
def gear_ratio
# Gear needs to know that the Wheel class exists deep inside the gear_ratio method.
radio * Wheel.new(rim, tire).diameter
end
end
# Good
class Gear
# using wheel as an argument cut the dependency
attr_deader :chainring, :cog, :wheel
def initialize(chainring, cog, wheel)
@chainring = chainring
@cog = cog
@wheel = wheel
end
def gear_ratio
radio * wheel.diameter
end
end
# ISOLATING DEPENDENCIES
# Assuming you can't change the arguments or cannot pass wheel to Gear
# Option 1: Move dependency from Gear ratio (see line 17) to Gear's initialization method.
class Gear
attr_reader :chainring, :cog, :rim, :tire
def initialize
@chainring = chainring
@cog = cog
@wheel = Wheel.new(rim, tire)
end
def gear_inches
ratio * wheel.diameter
end
end
# Option 2
# Lazily create a new instance of Wheel using ||= operator. New instance of wheel is deferred until gear_inches invokes wheel.
class Gear
attr_reader :chainring, :cog, :rim, :tire
def initialize
@chainring = chainring
@cog = cog
@rim = rim
@tire = tire
end
def gear_inches
ratio * wheel.diameter
end
def wheel
@wheel ||= Wheel.new(rim, tire)
end
end
# ISOLATE VULNERABLE EXTERNAL MESSAGES
# A good way to understand messages is by using the term "send".
# In ruby a method can be called by its name (example: ratio) or by sending the "ratio" message to it's parent class. (ex: Gear.new(args).send('ratio')
# BAD
def gear_inches
ratio * wheel.diameter
# otherwise written as: self.send('ratio') * wheel.send('diameter') "send ratio message to self. send diameter message to wheel'
end
# GOOD
def gear_inches
# some scary math
foo = some_intermediate_result * diameter
# more lines of scary math
end
def diameter
# encapsulate method from wheel in a method from the same class (self)
# in this way if the definition of diameter changes, you only need to change it here and not everywhere wheel.diameter appears
wheel.diameter
end
# REMOVE ARGUMENT-ORDER DEPENDENCIES
# BAD
class Gear
attr_reader :chainring, :cog, :wheel
def initialize(chainring, cog, wheel)
@chainring = chainring
@cog = cog
@wheel = wheel
end
end
# You explicitly need to know the order of the arguments
Gear.new(26, 1.5).gear_inches
# GOOD
# Option 1: Keyword arguments (not in 2013 edition)
def initialize(chainring:, cog:, :wheel)
# ...
end
Gear.new(chainring: 52, cog: 11, wheel: Wheel.new(26, 1.5))
# Option 2: using hashes
class Gear
attr_reader :chainring, :cog, :wheel
def initialize(args)
@chainring = args[:chainring]
@cog = args[:cog]
@wheel = args[:wheel]
end
end
Gear.new(chainring: 52, cog: 11, wheel: Wheel.new(26, 1.5))
# Explicitly defining defaults using ||
def initialize(args)
@chainring = args[:chainring] || 40
@cog = args[:cog] || 18
@wheel = args[:wheel]
end
# Explicitly defining defaults using fetch
def initialize(args)
@chainring = args.fecht(:chainring, 40)
@cog = args.fecht(:cog, 18)
@wheel = args[:wheel]
end
# isolate defaults in a separate method
def initialize(args)
args = defaults.merge(args)
@chainring = args[:chainring]
end
def defaults
{ chainring: 40, cog: 18 }
end
# if your defaults are more than simple nubmers or stirngs, implement a defaults method
# isolate multiparameter initialization
# when gear is part of an external interface
module SomeFramework
class Gear
attr_reader :chainring, :cog, :wheel
def initialize(chainring, cog, wheel)
@chainring = chainring
@cog = cog
@wheel = wheel
end
end
end
# YOUR WRAPPER: protect yourself from changes. The wrapper becomes the object you'll use in your code
module GearWrapper
def self.gear(args)
SomeFrawork::Gear.new(args[:chainring], args[:cog], args[:wheel])
end
end
# Managing Dependency direction