-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpolymorphism-and-conditionals.html
311 lines (297 loc) · 11.2 KB
/
polymorphism-and-conditionals.html
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Polymorphism and conditionals</title>
<link rel="stylesheet" href="css/css.css" type="text/css" media="all">
<link rel="stylesheet" href="css/prism.css" type="text/css" media="all">
<link rel="stylesheet" href="css/latex.css" type="text/css" media="all">
</head>
<body>
<h1 id="polymorphism-and-conditionals">Polymorphism and conditionals</h1>
<blockquote>
<p>Most <code>if</code>s can be replaced with polymorphism.</p>
</blockquote>
<h2 id="why-would-you-go-about-replacing-all-ifs%3F">Why would you go about replacing all <code>if</code>s?</h2>
<ul>
<li>functions without <code>if</code>s are easier to read and reason about</li>
<li>functions without <code>if</code>s are easier to test, as there is only one execution per code</li>
<li>polymorphic system is easier to maintain - easier to extend</li>
</ul>
<h2 id="when-to-use-polymorphism%3F">When to use polymorphism?</h2>
<ul>
<li>if an object should behave differently based on it's state</li>
<li>if the same condition is checked in multiple places</li>
</ul>
<h2 id="when-to-use-conditionals%3F">When to use conditionals?</h2>
<ul>
<li>in comparisons of primitive objects: <code>></code>,<code><</code>,<code>==</code>,<code>!=</code></li>
<li>when the comparison is part of business logic</li>
</ul>
<h2 id="how-to-be-if-free%3F">How to be <code>if</code> free?</h2>
<ul>
<li>do not return <code>null</code> or "nullable" types (php feature)</li>
<li>do not return error codes instead throw RunTime exception</li>
</ul>
<blockquote>
<p>Instead of returning <code>null</code> you can return <a href="https://en.wikipedia.org/wiki/Null_object_pattern">"null object"</a></p>
</blockquote>
<hr>
<h1 id="example-%231---state-based-behaviour.">Example #1 - state based behaviour.</h1>
<blockquote>
<p>When you have a conditional that chooses a different behaviour based on a <em>type</em> of an object,
then move each leg of conditional into an overriding method of a subclass and
make the original method abstract.</p>
</blockquote>
<p>Your task is to model this expression <span class="katex"><span class="katex-mathml"><math><semantics><mrow><mn>1</mn><mo>+</mo><mn>2</mn><mo>∗</mo><mn>3</mn></mrow><annotation encoding="application/x-tex">1+2*3</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="strut" style="height:0.64444em;"></span><span class="strut bottom" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="base"><span class="mord">1</span><span class="mord rule" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mord rule" style="margin-right:0.2222222222222222em;"></span><span class="mord">2</span><span class="mord rule" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mord rule" style="margin-right:0.2222222222222222em;"></span><span class="mord">3</span></span></span></span> as object:</p>
<ul>
<li>that can be evaluated</li>
<li>that can represent itself as string</li>
</ul>
<p>Such tree structure would be used to represent particular expression.</p>
<img src="img/26549dbb2238c8c1592fb6544ce46d9d.png" alt="uml diagram">
<p>Then "evaluate" and "toString" methods will be implemented.</p>
<pre><code class="language-php"><?php
class Node {
protected string $operator;
protected float $value;
protected ?Node $left;
protected ?Node $right;
public function evaluate(): float {
switch ($this->operator) {
case '#': return $this->value;
case '+': return $this->left->evaluate() + $this->right->evaluate();
case '*': return $this->left->evaluate() * $this->right->evaluate();
}
}
}
</code></pre>
<img src="img/9717e7b1354d0f1c04af19b9b45a5ad7.png" alt="uml diagram">
<p>Main problems with such careless implementation:</p>
<ul>
<li><code>toString</code> method <em>(once implemented)</em> would <strong>have to mimic</strong> this <code>case</code> statement <em>(code duplication)</em></li>
<li>when another operation is added - all of these <code>case</code> statements would require that update</li>
</ul>
<p>Analyzing attribute usage:</p>
<ul>
<li>if that is <em>operation node</em> - attributes <code>left</code>, <code>right</code> and <code>operator</code> are only used</li>
<li>if that is <em>value node</em> - only <code>value</code> attribute is used</li>
</ul>
<blockquote>
<table>
<thead>
<tr>
<th></th>
<th>#</th>
<th>+</th>
<th>*</th>
</tr>
</thead>
<tbody>
<tr>
<td>operator</td>
<td></td>
<td>y</td>
<td>y</td>
</tr>
<tr>
<td>value</td>
<td>y</td>
<td></td>
<td></td>
</tr>
<tr>
<td>left</td>
<td></td>
<td>y</td>
<td>y</td>
</tr>
<tr>
<td>right</td>
<td></td>
<td>y</td>
<td>y</td>
</tr>
</tbody>
</table>
<p>This table gives us a feeling that some kind of polymorphic behaviour <strong>is</strong> happening,
and it is happening through fields and <code>if</code> statements instead of polymorphism.</p>
</blockquote>
<p>Break them up:</p>
<img src="img/1f81a531a213a4f24d0bd2db5eb4b127.png" alt="uml diagram">
<blockquote>
<p>Now the <em>behaviour</em> of object is determined based on it's type - as it should be.</p>
</blockquote>
<p>See how simple is <code>evaluate</code> method implementation on <em>value node</em> has become.</p>
<pre><code class="language-php"><?php
class ValueNode implements Node {
// ..
public function evaluate(): float {
return $this->value;
}
}
</code></pre>
<p><code>evaluate</code> method on <em>operation node</em> is now a bit simpler too.</p>
<pre><code class="language-php"><?php
class OpNode implements Node {
// ..
public function evaluate(): float {
switch ($this->operator) {
case '+': return $this->left->evaluate() + $this->right->evaluate();
case '*': return $this->left->evaluate() * $this->right->evaluate();
}
}
}
</code></pre>
<p>What else just changed?</p>
<ul>
<li>we got rid of <code>null</code> values in <code>left</code> and <code>right</code> properties in <em>operation nodes</em></li>
<li>we got rid of that unused <code>value</code> property in <em>operation node</em></li>
</ul>
<p>But still there is that <code>case</code> statement.</p>
<blockquote>
<p>Think about how you would extend this - each time you add a new operator, <code>toString</code>
method will have to mimic <code>evaluate</code> method still.</p>
</blockquote>
<p>For this reason we break up <em>operation node</em> more.</p>
<pre><code class="language-php"><?php
abstract class OpNode implements Node {
protected Node $left;
protected Node $right;
}
class AdditionNode extends OpNode {
public function evaluate(): float {
return $this->left->evaluate() + $this->right->evaluate();
}
}
class MultiplicationNode extends OpNode {
public function evaluate(): float {
return $this->left->evaluate() * $this->right->evaluate();
}
}
</code></pre>
<img src="img/5002d0dda69b1b2aaa8ce8f376fa169c.png" alt="uml diagram">
<blockquote>
<p>Now there can be no null pointer exceptions.
Code is now more organized.
There is less clutter.
There is less cognitive load.
Maintainability and readability has improved.
Easy to introduce <code>toString</code> method into this system.
Easy to introduce new <code>%</code> operator (open/close principle).
It is now easier to write tests for this system.</p>
</blockquote>
<h1 id="example-%232---repeated-condition">Example #2 - repeated condition</h1>
<blockquote>
<p>When based on a state outside this object (i.e. some global constant),
object should behave a bit differently. How to re-factor?</p>
</blockquote>
<p>Here an <code>Update</code> object represents the update of some sort.
And there is global flag <code>FLAG_i18n_ENABLED</code> that determines if
internationalization is enabled or not.</p>
<pre><code class="language-php"><?php
class Update {
public function execute() {
if (FLAG_i18n_ENABLED) {
// Do A
} else {
// Do B
}
}
public function render() {
if (FLAG_i18n_ENABLED) {
// return A
} else {
// return B
}
}
}
</code></pre>
<img src="img/bb884cff582447bb905e09ec78575dce.png" alt="uml diagram">
<p>Notice the repeated conditional, seems like a maintenance burden.
Bugs will appear if someone is not careful enough. Rather choose this implementation:</p>
<pre><code class="language-php"><?php
interface Update {
public function execute();
public function render();
}
class I18NUpdate implements Update {
public function execute() {
// Do A
}
public function render() {
// return A
}
}
class NonI18NUpdate implements Update {
public function execute() {
// Do B
}
public function render() {
// return B
}
}
</code></pre>
<img src="img/afd41421d1a8b28e9f0cc8d6787fbba7.png" alt="uml diagram">
<blockquote>
<p>Keep your <code>if</code>s closer to the <em>edge of application</em>.</p>
</blockquote>
<p>Actually the <code>if</code> statement here <strong>did not</strong> disappear!
It got moved!</p>
<p>Because once you need to <em>consume</em> an <code>Update</code>
then you still need to decide which version to use.
Someone else is responsible for instantiating the <strong>correct</strong>
object, and the consumer does not care if it got passed as an
<em>i18n</em> or <em>noni18n</em> version.</p>
<pre><code class="language-php"><?php
class Factory {
public static function update(): Update {
return FLAG_i18n_ENABLED
? new I18NUpdate()
: new NonI18NUpdate();
}
}
class Consumer {
public function process(Update $update) {
$update->execute();
}
}
</code></pre>
<p>This leads us back to the good old <em>dependency injection</em>.</p>
<blockquote>
<h3 id="keep-two-piles-of-objects%3A">Keep two piles of objects:</h3>
<ul>
<li>pile of objects that <em>does business logic</em> and domain abstractions</li>
<li>pile of objects that <em>is construction responsible</em> for building object graphs</li>
</ul>
</blockquote>
<p>So the factory then decides to instantiate international version.</p>
<p>Now also we will not have to make the same decision over and over again
in cases when this code runs in some tight loop.</p>
<h1 id="example-%233---business-logic-has-to-behave-differently">Example #3 - business logic has to behave differently</h1>
<blockquote>
<p>By just wiring up objects differently you can change the behaviour of application.</p>
</blockquote>
<pre><code class="language-php"><?php
interface Connection {
public function write(string $txt);
}
class App {
protected Connection $connection;
public function notify(string $message) {
$this->connection->write($message);
}
}
$app = new App(new UnixConnection);
</code></pre>
<hr>
<blockquote>
<p>When you see a <code>case</code> statement - it usually is a pretty good indicator
that polymorphism begs to be there.
Try to write a project without <code>if</code>s. It should be good experience.
Meanwhile, try staying pragmatic.</p>
</blockquote>
<hr>
<script src="js/prism.js"></script>
</body>
</html>