-
Notifications
You must be signed in to change notification settings - Fork 0
/
feed.xml
1930 lines (1434 loc) · 197 KB
/
feed.xml
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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.0">Jekyll</generator><link href="https://nickhuangcyh.github.io/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://nickhuangcyh.github.io/blog/" rel="alternate" type="text/html" /><updated>2024-12-29T14:06:06+00:00</updated><id>https://nickhuangcyh.github.io/blog/feed.xml</id><title type="html">Nick’s Blog</title><subtitle>An amazing website.</subtitle><author><name>Nick Huang</name></author><entry><title type="html">Design Pattern (28) - Interpreter Pattern (解譯器模式)</title><link href="https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-28-interpreter-pattern/" rel="alternate" type="text/html" title="Design Pattern (28) - Interpreter Pattern (解譯器模式)" /><published>2024-12-29T08:30:00+00:00</published><updated>2024-12-29T08:30:00+00:00</updated><id>https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-28-interpreter-pattern</id><content type="html" xml:base="https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-28-interpreter-pattern/"><blockquote>
<p>您可於此 <a href="https://github.com/nickhuangcyh/design_pattern">design_pattern repo</a> 下載 Design Pattern 系列程式碼。</p>
</blockquote>
<hr />
<h2 id="需求">需求</h2>
<p>我們需要設計一個布林運算解譯系統,具備以下功能:</p>
<ol>
<li>能解譯包含布林值、AND 運算與 OR 運算的表達式。</li>
<li>符合開放關閉原則,能夠方便地新增其他運算(如 NOT)。</li>
<li>系統結構清晰,易於維護與擴展。</li>
</ol>
<hr />
<h2 id="物件導向分析-ooa">物件導向分析 (OOA)</h2>
<p>理解需求後,讓我們來快速實作物件導向分析吧!</p>
<p><img src="/blog/assets/images/design_pattern_interpreter_pattern_uml_1.png" alt="interpreter_pattern_uml_1" /></p>
<h3 id="察覺-forces">察覺 Forces</h3>
<ol>
<li>
<p><strong>複雜性增加</strong></p>
<ul>
<li>隨著運算符類型增加,手動解析邏輯會變得難以維護。</li>
</ul>
</li>
<li>
<p><strong>重複代碼</strong></p>
<ul>
<li>不同運算符的處理可能導致類似功能重複實現。</li>
</ul>
</li>
<li>
<p><strong>難以擴展</strong></p>
<ul>
<li>新增運算符需要修改大量代碼,違反開放關閉原則 (OCP)。</li>
</ul>
</li>
</ol>
<hr />
<h2 id="套用-interpreter-pattern-solution-得到新的-context-resulting-context">套用 Interpreter Pattern (Solution) 得到新的 Context (Resulting Context)</h2>
<p>做完 OOA,察覺 Forces,看清楚整個 Context 後,就可以來套用 Interpreter Pattern 解決這個問題。</p>
<p>先來看一下 Interpreter Pattern 的 UML</p>
<p><img src="/blog/assets/images/design_pattern_interpreter_pattern_uml_2.png" alt="interpreter_pattern_uml_2" /></p>
<h3 id="interrepter-pattern-的組件">Interrepter Pattern 的組件</h3>
<ol>
<li>
<p><strong>建立抽象表達式 (Expression)</strong></p>
<ul>
<li>定義所有表達式的通用介面,確保不同類型的表達式可以被統一處理。</li>
</ul>
</li>
<li>
<p><strong>設計終端表達式 (Terminal Expression)</strong></p>
<ul>
<li>負責處理語法中的基本單位(如布林值 <code class="language-plaintext highlighter-rouge">true</code> 和 <code class="language-plaintext highlighter-rouge">false</code>)。</li>
</ul>
</li>
<li>
<p><strong>設計非終端表達式 (Non-Terminal Expression)</strong></p>
<ul>
<li>表示複雜運算的組合(如 <code class="language-plaintext highlighter-rouge">AND</code> 和 <code class="language-plaintext highlighter-rouge">OR</code>),遞迴處理子表達式。</li>
</ul>
</li>
</ol>
<p>為了解決上述問題,我們採用解譯器模式來建構布林運算系統。核心思想是將每個運算符與操作數作為一個 “表達式”,並使用遞迴的方式進行解譯。</p>
<p>透過這種方式,我們可以將複雜的布林運算拆解為多個小型且可組合的單元,並保持系統結構的靈活性。</p>
<p>將 Interpreter Pattern 套用到我們的應用吧</p>
<p><img src="/blog/assets/images/design_pattern_interpreter_pattern_uml_3.png" alt="interpreter_pattern_uml_3" /></p>
<hr />
<h2 id="物件導向程式設計-oop">物件導向程式設計 (OOP)</h2>
<p>[抽象表達式: Expression]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">Expression</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">interpret</span><span class="p">():</span> <span class="nc">Boolean</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[終端表達式: BooleanValue]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">BooleanValue</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">value</span><span class="p">:</span> <span class="nc">Boolean</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Expression</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">interpret</span><span class="p">():</span> <span class="nc">Boolean</span> <span class="p">=</span> <span class="n">value</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[非終端表達式: AndExpression, OrExpression]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">AndExpression</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">left</span><span class="p">:</span> <span class="nc">Expression</span><span class="p">,</span> <span class="k">private</span> <span class="kd">val</span> <span class="py">right</span><span class="p">:</span> <span class="nc">Expression</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Expression</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">interpret</span><span class="p">():</span> <span class="nc">Boolean</span> <span class="p">=</span> <span class="n">left</span><span class="p">.</span><span class="nf">interpret</span><span class="p">()</span> <span class="p">&amp;&amp;</span> <span class="n">right</span><span class="p">.</span><span class="nf">interpret</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">OrExpression</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">left</span><span class="p">:</span> <span class="nc">Expression</span><span class="p">,</span> <span class="k">private</span> <span class="kd">val</span> <span class="py">right</span><span class="p">:</span> <span class="nc">Expression</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Expression</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">interpret</span><span class="p">():</span> <span class="nc">Boolean</span> <span class="p">=</span> <span class="n">left</span><span class="p">.</span><span class="nf">interpret</span><span class="p">()</span> <span class="p">||</span> <span class="n">right</span><span class="p">.</span><span class="nf">interpret</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[客戶端代碼: Client]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// 定義布林表達式:true AND false OR true</span>
<span class="kd">val</span> <span class="py">expression</span> <span class="p">=</span> <span class="nc">OrExpression</span><span class="p">(</span>
<span class="nc">AndExpression</span><span class="p">(</span>
<span class="nc">BooleanValue</span><span class="p">(</span><span class="k">true</span><span class="p">),</span>
<span class="nc">BooleanValue</span><span class="p">(</span><span class="k">false</span><span class="p">)</span>
<span class="p">),</span>
<span class="nc">BooleanValue</span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
<span class="p">)</span>
<span class="c1">// 計算結果</span>
<span class="kd">val</span> <span class="py">result</span> <span class="p">=</span> <span class="n">expression</span><span class="p">.</span><span class="nf">interpret</span><span class="p">()</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"Result of the expression is: $result"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Output]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Result</span> <span class="n">of</span> <span class="n">the</span> <span class="n">expression</span> <span class="k">is</span><span class="p">:</span> <span class="k">true</span>
</code></pre></div></div>
<hr />
<h2 id="結論">結論</h2>
<p>透過解譯器模式,我們成功解決了布林運算系統的設計挑戰,並實現以下優勢:</p>
<ol>
<li>
<p><strong>結構清晰</strong></p>
<ul>
<li>將每個運算符和操作數封裝成表達式類別,便於組合與管理。</li>
</ul>
</li>
<li>
<p><strong>易於擴展</strong></p>
<ul>
<li>新增運算符只需實現新的表達式類別,符合開放關閉原則 (OCP)。</li>
</ul>
</li>
<li>
<p><strong>靈活性高</strong></p>
<ul>
<li>支持動態構建與解譯複雜表達式,適用於多種運算場景。</li>
</ul>
</li>
</ol>
<p>需要注意的是,解譯器模式更適合處理結構簡單的語法。如果語法過於複雜,可能導致類別數量激增,這時可以考慮結合其他模式(如組合模式或訪問者模式)進行優化。</p></content><author><name>Nick Huang</name></author><category term="Design Pattern" /><category term="Interpreter Pattern" /><summary type="html">解譯器模式用於構建一個可解讀特定語言或語法的系統,適合於處理複雜的規則判斷或指令語法。</summary></entry><entry><title type="html">Design Pattern (27) - Visitor Pattern (訪問者模式)</title><link href="https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-27-visitor-pattern/" rel="alternate" type="text/html" title="Design Pattern (27) - Visitor Pattern (訪問者模式)" /><published>2024-12-28T13:30:00+00:00</published><updated>2024-12-28T13:30:00+00:00</updated><id>https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-27-visitor-pattern</id><content type="html" xml:base="https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-27-visitor-pattern/"><blockquote>
<p>您可於此 <a href="https://github.com/nickhuangcyh/design_pattern">design_pattern repo</a> 下載 Design Pattern 系列程式碼。</p>
</blockquote>
<hr />
<h2 id="需求">需求</h2>
<p>在設計一個 <strong>IoT App 整合多品牌 IPCam 的功能</strong> 時,我們需要滿足以下需求:</p>
<ol>
<li>支援多種 IPCam 品牌,這些品牌的 IPCam 提供不同的串流與截圖方式:
<ul>
<li><strong>HIKVISION</strong> 提供 RTSP 協定,可以用通用方式播放串流與截圖。</li>
<li><strong>DAHUA</strong> 提供自家 SDK,需要依賴 SDK 提供的方法進行操作。</li>
</ul>
</li>
<li><strong>App 的程式碼結構不應依賴 IPCam 品牌的實現細節</strong>,應保持開放擴展性,方便後續新增新的 IPCam 品牌。</li>
<li><strong>避免修改 IPCam 的核心結構</strong>,因為這些品牌的實現通常由廠商提供,無法直接修改。</li>
</ol>
<hr />
<h2 id="物件導向分析-ooa">物件導向分析 (OOA)</h2>
<p>理解需求後,讓我們來快速實作物件導向分析吧!</p>
<p><img src="/blog/assets/images/design_pattern_visitor_pattern_uml_1.png" alt="visitor_pattern_uml_1" /></p>
<h3 id="察覺-forces">察覺 Forces</h3>
<p>如果未套用設計模式,我們可能會遇到以下問題:</p>
<ol>
<li><strong>難以擴展新品牌</strong>
<ul>
<li>每新增一個品牌的 IPCam,就需要修改 App 的核心邏輯。</li>
</ul>
</li>
<li><strong>違反開放關閉原則 (OCP)</strong>
<ul>
<li>核心邏輯與品牌實現細節耦合,新增功能需要修改核心程式碼。</li>
</ul>
</li>
<li><strong>無法統一處理不同品牌的操作</strong>
<ul>
<li>每個品牌的串流與截圖方式不同,導致程式碼混亂,難以維護。</li>
</ul>
</li>
</ol>
<hr />
<h2 id="套用-visitor-pattern-solution-得到新的-context-resulting-context">套用 Visitor Pattern (Solution) 得到新的 Context (Resulting Context)</h2>
<p>做完 OOA,察覺 Forces,看清楚整個 Context 後,就可以來套用 Visitor Pattern 解決這個問題。</p>
<p>先來看一下 Visitor Pattern 的 UML</p>
<p><img src="/blog/assets/images/design_pattern_visitor_pattern_uml_2.png" alt="visitor_pattern_uml_2" /></p>
<h3 id="visitor-pattern-的組件">Visitor Pattern 的組件</h3>
<p>訪問者模式的核心組件包括:</p>
<ol>
<li>
<p><strong>Visitor (訪問者介面)</strong></p>
<ul>
<li>定義對每種類型物件的操作方法。</li>
</ul>
</li>
<li>
<p><strong>ConcreteVisitor (具體訪問者)</strong></p>
<ul>
<li>實現特定操作邏輯。</li>
</ul>
</li>
<li>
<p><strong>Element (元素介面)</strong></p>
<ul>
<li>定義接受訪問者的方法 (<code class="language-plaintext highlighter-rouge">accept</code>),並將訪問者傳遞給自己。</li>
</ul>
</li>
<li>
<p><strong>ConcreteElement (具體元素)</strong></p>
<ul>
<li>實現接受訪問者的方法,讓訪問者能夠訪問並操作具體元素。</li>
</ul>
</li>
</ol>
<p>將 Visitor Pattern 套用到我們的應用吧</p>
<p><img src="/blog/assets/images/design_pattern_visitor_pattern_uml_3.png" alt="visitor_pattern_uml_3" /></p>
<hr />
<h2 id="物件導向設計-oop">物件導向設計 (OOP)</h2>
<p>[Element: IPCam]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">IPCam</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">accept</span><span class="p">(</span><span class="n">visitor</span><span class="p">:</span> <span class="nc">IPCamVisitor</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[ConcreteElements: HikvisionIPCam, DahuaIPCam]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">HikvisionIPCam</span> <span class="p">:</span> <span class="nc">IPCam</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">accept</span><span class="p">(</span><span class="n">visitor</span><span class="p">:</span> <span class="nc">IPCamVisitor</span><span class="p">)</span> <span class="p">{</span>
<span class="n">visitor</span><span class="p">.</span><span class="nf">visitHikvision</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">fun</span> <span class="nf">getRTSPStream</span><span class="p">():</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"rtsp://hikvision/stream"</span>
<span class="p">}</span>
<span class="k">fun</span> <span class="nf">captureSnapshot</span><span class="p">():</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"Hikvision Snapshot"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">DahuaIPCam</span> <span class="p">:</span> <span class="nc">IPCam</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">accept</span><span class="p">(</span><span class="n">visitor</span><span class="p">:</span> <span class="nc">IPCamVisitor</span><span class="p">)</span> <span class="p">{</span>
<span class="n">visitor</span><span class="p">.</span><span class="nf">visitDahua</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">fun</span> <span class="nf">startSDKStream</span><span class="p">():</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"Dahua SDK Stream"</span>
<span class="p">}</span>
<span class="k">fun</span> <span class="nf">takeSDKSnapshot</span><span class="p">():</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"Dahua Snapshot"</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Visitor: IPCamVisitor]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">IPCamVisitor</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">visitHikvision</span><span class="p">(</span><span class="n">ipCam</span><span class="p">:</span> <span class="nc">HikvisionIPCam</span><span class="p">)</span>
<span class="k">fun</span> <span class="nf">visitDahua</span><span class="p">(</span><span class="n">ipCam</span><span class="p">:</span> <span class="nc">DahuaIPCam</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[ConcreteVisitors: IPCamStreamingVisitor, IPCamSnapshotVisitor]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">IPCamStreamingVisitor</span> <span class="p">:</span> <span class="nc">IPCamVisitor</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">visitHikvision</span><span class="p">(</span><span class="n">ipCam</span><span class="p">:</span> <span class="nc">HikvisionIPCam</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"Streaming: ${ipCam.getRTSPStream()}"</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">visitDahua</span><span class="p">(</span><span class="n">ipCam</span><span class="p">:</span> <span class="nc">DahuaIPCam</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"Streaming: ${ipCam.startSDKStream()}"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">IPCamSnapshotVisitor</span> <span class="p">:</span> <span class="nc">IPCamVisitor</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">visitHikvision</span><span class="p">(</span><span class="n">ipCam</span><span class="p">:</span> <span class="nc">HikvisionIPCam</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"Snapshot: ${ipCam.captureSnapshot()}"</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">visitDahua</span><span class="p">(</span><span class="n">ipCam</span><span class="p">:</span> <span class="nc">DahuaIPCam</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"Snapshot: ${ipCam.takeSDKSnapshot()}"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Client]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">ipCams</span><span class="p">:</span> <span class="nc">List</span><span class="p">&lt;</span><span class="nc">IPCam</span><span class="p">&gt;</span> <span class="p">=</span> <span class="nf">listOf</span><span class="p">(</span><span class="nc">HikvisionIPCam</span><span class="p">(),</span> <span class="nc">DahuaIPCam</span><span class="p">())</span>
<span class="kd">val</span> <span class="py">streamingVisitor</span> <span class="p">=</span> <span class="nc">IPCamStreamingVisitor</span><span class="p">()</span>
<span class="kd">val</span> <span class="py">snapshotVisitor</span> <span class="p">=</span> <span class="nc">IPCamSnapshotVisitor</span><span class="p">()</span>
<span class="k">for</span> <span class="p">(</span><span class="n">ipCam</span> <span class="k">in</span> <span class="n">ipCams</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ipCam</span><span class="p">.</span><span class="nf">accept</span><span class="p">(</span><span class="n">streamingVisitor</span><span class="p">)</span>
<span class="n">ipCam</span><span class="p">.</span><span class="nf">accept</span><span class="p">(</span><span class="n">snapshotVisitor</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Output]</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Streaming: rtsp://hikvision/stream
Snapshot: Hikvision Snapshot
Streaming: Dahua SDK Stream
Snapshot: Dahua Snapshot
</code></pre></div></div>
<hr />
<h2 id="結論">結論</h2>
<p>透過 Visitor Pattern,我們成功將不同品牌 IPCam 的操作邏輯與物件結構分離,並實現以下優勢:</p>
<ol>
<li>
<p><strong>易於擴展新品牌</strong></p>
<ul>
<li>新增品牌只需實作新的 <code class="language-plaintext highlighter-rouge">ConcreteElement</code> 類別,並在訪問者中新增相應的操作方法。</li>
</ul>
</li>
<li>
<p><strong>操作邏輯集中</strong></p>
<ul>
<li>不同品牌的操作邏輯集中於訪問者中,便於維護與管理。</li>
</ul>
</li>
<li>
<p><strong>符合設計原則</strong></p>
<ul>
<li>單一職責原則 (SRP):操作邏輯與物件結構分離。</li>
<li>開放關閉原則 (OCP):允許新增功能而不修改既有程式碼。</li>
</ul>
</li>
</ol>
<p>訪問者模式非常適合處理以下場景:</p>
<ul>
<li>多種類型物件需要執行不同操作。</li>
<li>物件結構穩定,但操作邏輯經常變化。</li>
</ul>
<p>訪問者模式為多變操作提供了一個優雅的解決方案,確保系統具有高擴展性與靈活性。</p></content><author><name>Nick Huang</name></author><category term="Design Pattern" /><category term="Visitor Pattern" /><summary type="html">訪問者模式提供了一種方式,讓我們能在不修改物件結構的前提下,為其增加新的操作邏輯,實現高擴展性。</summary></entry><entry><title type="html">Design Pattern (26) - Template Method Pattern (模板方法模式)</title><link href="https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-26-template-method-pattern/" rel="alternate" type="text/html" title="Design Pattern (26) - Template Method Pattern (模板方法模式)" /><published>2024-12-28T11:30:00+00:00</published><updated>2024-12-28T11:30:00+00:00</updated><id>https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-26-template-method-pattern</id><content type="html" xml:base="https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-26-template-method-pattern/"><blockquote>
<p>您可於此 <a href="https://github.com/nickhuangcyh/design_pattern">design_pattern repo</a> 下載 Design Pattern 系列程式碼。</p>
</blockquote>
<hr />
<h2 id="需求">需求</h2>
<p>在設計一個 <strong>資料格式轉換系統</strong> 時,我們需要滿足以下需求:</p>
<ol>
<li>支援多種格式轉換,例如:
<ul>
<li><strong>JSON 格式轉換</strong>:將資料轉換為 JSON 格式。</li>
<li><strong>XML 格式轉換</strong>:將資料轉換為 XML 格式。</li>
<li><strong>CSV 格式轉換</strong>:將資料轉換為 CSV 格式。</li>
</ul>
</li>
<li>系統需具備良好的擴展性:
<ul>
<li>能夠方便地新增新的格式轉換方式。</li>
</ul>
</li>
<li><strong>保持轉換流程核心一致性</strong>,例如:
<ul>
<li>所有格式轉換都需要:讀取資料、格式化資料、輸出資料。</li>
</ul>
</li>
<li><strong>避免重複程式碼</strong>。</li>
</ol>
<hr />
<h2 id="物件導向分析-ooa">物件導向分析 (OOA)</h2>
<p>理解需求後,讓我們來快速實作物件導向分析吧!</p>
<p><img src="/blog/assets/images/design_pattern_template_method_pattern_uml_1.png" alt="template_method_pattern_uml_1" /></p>
<h3 id="察覺-forces">察覺 Forces</h3>
<p>如果未套用設計模式,我們可能會遇到以下問題:</p>
<ol>
<li><strong>程式碼重複</strong>
<ul>
<li>每種格式的轉換邏輯中包含相同步驟,但被多次重複實作。</li>
</ul>
</li>
<li><strong>違反開放關閉原則 (OCP)</strong>
<ul>
<li>新增格式需要修改核心轉換邏輯。</li>
</ul>
</li>
<li><strong>難以維護與擴展</strong>
<ul>
<li>各格式轉換邏輯分散,難以統一管理與修改。</li>
</ul>
</li>
</ol>
<hr />
<h2 id="套用-template-method-pattern-solution-得到新的-context-resulting-context">套用 Template Method Pattern (Solution) 得到新的 Context (Resulting Context)</h2>
<p>做完 OOA,察覺 Forces,看清楚整個 Context 後,就可以來套用 Template Method Pattern 解決這個問題。</p>
<p>先來看一下 Template Method Pattern 的 UML</p>
<p><img src="/blog/assets/images/design_pattern_template_method_pattern_uml_2.png" alt="template_method_pattern_uml_2" /></p>
<h3 id="template-method-pattern-的組件">Template Method Pattern 的組件</h3>
<p>模板方法模式的核心組件包括:</p>
<ol>
<li>
<p><strong>AbstractClass (抽象類別)</strong></p>
<ul>
<li>定義模板方法 (Template Method),封裝核心流程。</li>
<li>提供部分步驟的預設實作,或將其標記為抽象,由子類別實現。</li>
</ul>
</li>
<li>
<p><strong>ConcreteClass (具體類別)</strong></p>
<ul>
<li>繼承抽象類別,實現具體步驟。</li>
</ul>
</li>
</ol>
<p>以下是 Template Method Pattern 的 UML 圖:</p>
<p><img src="/blog/assets/images/design_pattern_template_method_pattern_uml_3.png" alt="template_method_pattern_uml_3" /></p>
<hr />
<h2 id="物件導向設計-oop">物件導向設計 (OOP)</h2>
<p>[AbstractClass: DataFormatter]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">abstract</span> <span class="kd">class</span> <span class="nc">DataFormatter</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">convert</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nc">Map</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">Any</span><span class="p">&gt;):</span> <span class="nc">String</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">rawData</span> <span class="p">=</span> <span class="nf">readData</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">formattedData</span> <span class="p">=</span> <span class="nf">formatData</span><span class="p">(</span><span class="n">rawData</span><span class="p">)</span>
<span class="k">return</span> <span class="nf">outputData</span><span class="p">(</span><span class="n">formattedData</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">fun</span> <span class="nf">readData</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nc">Map</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">Any</span><span class="p">&gt;):</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">data</span><span class="p">.</span><span class="nf">toString</span><span class="p">()</span>
<span class="p">}</span>
<span class="c1">// subclass implementation</span>
<span class="k">protected</span> <span class="k">abstract</span> <span class="k">fun</span> <span class="nf">formatData</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">String</span>
<span class="k">protected</span> <span class="k">abstract</span> <span class="k">fun</span> <span class="nf">outputData</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">String</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[ConcreteClasses: JsonFormatter, XmlFormatter, CsvFormatter]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">JsonFormatter</span> <span class="p">:</span> <span class="nc">DataFormatter</span><span class="p">()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">formatData</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"{\"data\": \"$data\"}"</span> <span class="c1">// 模擬 JSON 格式化</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">outputData</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"JSON Output: $data"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">XmlFormatter</span> <span class="p">:</span> <span class="nc">DataFormatter</span><span class="p">()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">formatData</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"&lt;data&gt;$data&lt;/data&gt;"</span> <span class="c1">// 模擬 XML 格式化</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">outputData</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"XML Output: $data"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">CsvFormatter</span> <span class="p">:</span> <span class="nc">DataFormatter</span><span class="p">()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">formatData</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">data</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="s">", "</span><span class="p">,</span> <span class="s">"\n"</span><span class="p">)</span> <span class="c1">// 模擬 CSV 格式化</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">outputData</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"CSV Output: $data"</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Client]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">data</span> <span class="p">=</span> <span class="nf">mapOf</span><span class="p">(</span><span class="s">"name"</span> <span class="n">to</span> <span class="s">"John"</span><span class="p">,</span> <span class="s">"age"</span> <span class="n">to</span> <span class="mi">30</span><span class="p">,</span> <span class="s">"city"</span> <span class="n">to</span> <span class="s">"New York"</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">jsonFormatter</span> <span class="p">=</span> <span class="nc">JsonFormatter</span><span class="p">()</span>
<span class="nf">println</span><span class="p">(</span><span class="n">jsonFormatter</span><span class="p">.</span><span class="nf">convert</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
<span class="kd">val</span> <span class="py">xmlFormatter</span> <span class="p">=</span> <span class="nc">XmlFormatter</span><span class="p">()</span>
<span class="nf">println</span><span class="p">(</span><span class="n">xmlFormatter</span><span class="p">.</span><span class="nf">convert</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
<span class="kd">val</span> <span class="py">csvFormatter</span> <span class="p">=</span> <span class="nc">CsvFormatter</span><span class="p">()</span>
<span class="nf">println</span><span class="p">(</span><span class="n">csvFormatter</span><span class="p">.</span><span class="nf">convert</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Output]</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JSON Output: {"data": "{name=John, age=30, city=New York}"}
XML Output: &lt;data&gt;{name=John, age=30, city=New York}&lt;/data&gt;
CSV Output: name=John\nage=30\ncity=New York
</code></pre></div></div>
<hr />
<h2 id="結論">結論</h2>
<p>透過 Template Method Pattern,我們成功將資料格式轉換的通用邏輯與變化邏輯分離,並實現以下優勢:</p>
<ol>
<li>
<p><strong>程式碼復用性高</strong></p>
<ul>
<li>通用的轉換流程邏輯在抽象類別中實現,避免重複。</li>
</ul>
</li>
<li>
<p><strong>易於擴展</strong></p>
<ul>
<li>新增格式只需繼承抽象類別並實現特定步驟。</li>
</ul>
</li>
<li>
<p><strong>符合設計原則</strong></p>
<ul>
<li>單一職責原則 (SRP):核心流程與特定邏輯分離。</li>
<li>開放關閉原則 (OCP):允許新增功能而不修改既有程式碼。</li>
</ul>
</li>
</ol>
<p>模板方法模式非常適合處理以下場景:</p>
<ul>
<li>不同的資料格式轉換流程。</li>
<li>文檔生成流程 (例如:PDF、Excel)。</li>
<li>多種資料處理的過程。</li>
</ul>
<p>模板方法模式確保系統核心流程的一致性,為實現靈活且高效的擴展提供了一個優雅的解決方案。</p></content><author><name>Nick Huang</name></author><category term="Design Pattern" /><category term="Template Method Pattern" /><summary type="html">模板方法模式提供了一個框架,允許子類別重新定義特定步驟的實作,保持核心流程的一致性,實現高復用性與靈活性。</summary></entry><entry><title type="html">Design Pattern (25) - Strategy Pattern (策略模式)</title><link href="https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-25-strategy-pattern/" rel="alternate" type="text/html" title="Design Pattern (25) - Strategy Pattern (策略模式)" /><published>2024-12-26T15:50:00+00:00</published><updated>2024-12-26T15:50:00+00:00</updated><id>https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-25-strategy-pattern</id><content type="html" xml:base="https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-25-strategy-pattern/"><blockquote>
<p>您可於此 <a href="https://github.com/nickhuangcyh/design_pattern">design_pattern repo</a> 下載 Design Pattern 系列程式碼。</p>
</blockquote>
<hr />
<h2 id="需求">需求</h2>
<p>在設計一個 <strong>電商運費計算系統</strong> 時,我們需要滿足以下需求:</p>
<ol>
<li>支援多種運費計算方式,例如:
<ul>
<li><strong>一般配送</strong>:固定運費。</li>
<li><strong>快速配送</strong>:依重量計費。</li>
<li><strong>國際配送</strong>:根據地區與重量計費。</li>
</ul>
</li>
<li>系統需具備良好的擴展性:
<ul>
<li>能夠方便地新增新的運費計算方式。</li>
</ul>
</li>
<li><strong>避免使用大量的 if-else 或 switch-case</strong>。</li>
<li>使用者應能輕鬆切換運費計算方式。</li>
</ol>
<hr />
<h2 id="物件導向分析-ooa">物件導向分析 (OOA)</h2>
<p>理解需求後,讓我們來快速實作物件導向分析吧!</p>
<p><img src="/blog/assets/images/design_pattern_strategy_pattern_uml_1.png" alt="strategy_pattern_uml_1" /></p>
<h3 id="察覺-forces">察覺 Forces</h3>
<p>如果未套用設計模式,我們可能會遇到以下問題:</p>
<ol>
<li>
<p><strong>難以維護</strong></p>
<ul>
<li>運費計算邏輯混雜在主程式內,新增或修改一種計算方式可能會影響其他部分。</li>
</ul>
</li>
<li>
<p><strong>違反開放關閉原則 (OCP)</strong></p>
<ul>
<li>每次新增運費計算方式都需修改核心業務邏輯。</li>
</ul>
</li>
<li>
<p><strong>違反單一職責原則 (SRP)</strong></p>
<ul>
<li>主程式同時負責運費計算與核心業務邏輯,責任過於繁重。</li>
</ul>
</li>
</ol>
<hr />
<h2 id="套用-strategy-pattern-solution-得到新的-context-resulting-context">套用 Strategy Pattern (Solution) 得到新的 Context (Resulting Context)</h2>
<p>做完 OOA,察覺 Forces,看清楚整個 Context 後,就可以來套用 Strategy Pattern 解決這個問題</p>
<p>先來看一下 Strategy Pattern 的 UML</p>
<p><img src="/blog/assets/images/design_pattern_strategy_pattern_uml_2.png" alt="strategy_pattern_uml_2" /></p>
<h3 id="strategy-pattern-的組件">Strategy Pattern 的組件</h3>
<p>策略模式的核心組件包括:</p>
<ol>
<li>
<p><strong>Strategy (策略介面)</strong><br />
定義所有策略需要實現的行為。</p>
</li>
<li>
<p><strong>ConcreteStrategy (具體策略)</strong><br />
每個具體策略類別實現特定的行為邏輯。</p>
</li>
<li>
<p><strong>Context (上下文)</strong><br />
維護一個策略物件,並根據當前策略執行對應行為。</p>
</li>
</ol>
<p>將 Strategy Pattern 套用到我們的應用吧</p>
<p><img src="/blog/assets/images/design_pattern_strategy_pattern_uml_3.png" alt="strategy_pattern_uml_3" /></p>
<hr />
<h2 id="物件導向設計-oop">物件導向設計 (OOP)</h2>
<p>[Strategy: ShippingStrategy]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">ShippingStrategy</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">calculateShippingCost</span><span class="p">(</span><span class="n">weight</span><span class="p">:</span> <span class="nc">Double</span><span class="p">,</span> <span class="n">region</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">Double</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[ConcreteStrategies: RegularShipping, ExpressShipping, InternationalShipping]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">RegularShipping</span> <span class="p">:</span> <span class="nc">ShippingStrategy</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">calculateShippingCost</span><span class="p">(</span><span class="n">weight</span><span class="p">:</span> <span class="nc">Double</span><span class="p">,</span> <span class="n">region</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">Double</span> <span class="p">{</span>
<span class="k">return</span> <span class="mf">50.0</span> <span class="c1">// 固定運費</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">ExpressShipping</span> <span class="p">:</span> <span class="nc">ShippingStrategy</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">calculateShippingCost</span><span class="p">(</span><span class="n">weight</span><span class="p">:</span> <span class="nc">Double</span><span class="p">,</span> <span class="n">region</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">Double</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">weight</span> <span class="p">*</span> <span class="mi">10</span> <span class="c1">// 每公斤 10 元</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">InternationalShipping</span> <span class="p">:</span> <span class="nc">ShippingStrategy</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">calculateShippingCost</span><span class="p">(</span><span class="n">weight</span><span class="p">:</span> <span class="nc">Double</span><span class="p">,</span> <span class="n">region</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">Double</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">regionMultiplier</span> <span class="p">=</span> <span class="k">when</span> <span class="p">(</span><span class="n">region</span><span class="p">)</span> <span class="p">{</span>
<span class="s">"Asia"</span> <span class="p">-&gt;</span> <span class="mi">15</span>
<span class="s">"Europe"</span> <span class="p">-&gt;</span> <span class="mi">20</span>
<span class="s">"America"</span> <span class="p">-&gt;</span> <span class="mi">25</span>
<span class="k">else</span> <span class="p">-&gt;</span> <span class="mi">30</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">weight</span> <span class="p">*</span> <span class="n">regionMultiplier</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Context: ShippingCalculator]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ShippingCalculator</span><span class="p">(</span><span class="k">private</span> <span class="kd">var</span> <span class="py">strategy</span><span class="p">:</span> <span class="nc">ShippingStrategy</span><span class="p">)</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">setStrategy</span><span class="p">(</span><span class="n">strategy</span><span class="p">:</span> <span class="nc">ShippingStrategy</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="n">strategy</span> <span class="p">=</span> <span class="n">strategy</span>
<span class="p">}</span>
<span class="k">fun</span> <span class="nf">calculateCost</span><span class="p">(</span><span class="n">weight</span><span class="p">:</span> <span class="nc">Double</span><span class="p">,</span> <span class="n">region</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">Double</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">strategy</span><span class="p">.</span><span class="nf">calculateShippingCost</span><span class="p">(</span><span class="n">weight</span><span class="p">,</span> <span class="n">region</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Client]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">calculator</span> <span class="p">=</span> <span class="nc">ShippingCalculator</span><span class="p">(</span><span class="nc">RegularShipping</span><span class="p">())</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"一般配送運費: ${calculator.calculateCost(5.0, "</span><span class="nc">Asia</span><span class="s">")} 元"</span><span class="p">)</span> <span class="c1">// 固定 50 元</span>
<span class="n">calculator</span><span class="p">.</span><span class="nf">setStrategy</span><span class="p">(</span><span class="nc">ExpressShipping</span><span class="p">())</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"快速配送運費: ${calculator.calculateCost(5.0, "</span><span class="nc">Asia</span><span class="s">")} 元"</span><span class="p">)</span> <span class="c1">// 5.0 * 10 = 50 元</span>
<span class="n">calculator</span><span class="p">.</span><span class="nf">setStrategy</span><span class="p">(</span><span class="nc">InternationalShipping</span><span class="p">())</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"國際配送運費 (Asia): ${calculator.calculateCost(5.0, "</span><span class="nc">Asia</span><span class="s">")} 元"</span><span class="p">)</span> <span class="c1">// 5.0 * 15 = 75 元</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Output]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">一般配送運費</span><span class="p">:</span> <span class="mf">50.0</span> <span class="err">元</span>
<span class="err">快速配送運費</span><span class="p">:</span> <span class="mf">50.0</span> <span class="err">元</span>
<span class="err">國際配送運費</span> <span class="p">(</span><span class="nc">Asia</span><span class="p">):</span> <span class="mf">75.0</span> <span class="err">元</span>
</code></pre></div></div>
<h2 id="結論">結論</h2>
<p>透過 Strategy Pattern,我們成功將運費計算邏輯與核心功能分離,並實現以下優勢:</p>
<ol>
<li>易於擴展</li>
</ol>
<ul>
<li>新增運費計算方式只需實作新的策略類別,無需修改現有程式碼。</li>
</ul>
<ol>
<li>低耦合性</li>
</ol>
<ul>
<li>運費計算邏輯與核心業務邏輯分離,各自負責自己的功能。</li>
</ul>
<ol>
<li>符合設計原則</li>
</ol>
<ul>
<li>單一職責原則 (SRP):每個策略類別專注於特定運費計算邏輯。</li>
<li>開放關閉原則 (OCP):策略模式允許在不修改現有程式碼的情況下,新增新功能。</li>
</ul>
<p>策略模式非常適合處理需要根據條件執行不同行為的場景,例如:</p>
<ul>
<li>不同的折扣策略 (滿額折扣、季節性優惠)。</li>
<li>不同的排序算法 (快速排序、合併排序)。</li>
<li>各類繳稅計算方式。</li>
</ul>
<p>策略模式讓系統更具彈性,為複雜問題提供了一個優雅的解決方案。</p></content><author><name>Nick Huang</name></author><category term="Design Pattern" /><category term="Strategy Pattern" /><summary type="html">策略模式提供了一種靈活的解決方案,讓系統能根據需求動態切換不同的行為邏輯,實現高可擴展性與低耦合性。</summary></entry><entry><title type="html">Design Pattern (24) - State Pattern (狀態模式)</title><link href="https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-24-state-pattern/" rel="alternate" type="text/html" title="Design Pattern (24) - State Pattern (狀態模式)" /><published>2024-12-22T07:00:00+00:00</published><updated>2024-12-22T07:00:00+00:00</updated><id>https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-24-state-pattern</id><content type="html" xml:base="https://nickhuangcyh.github.io/blog/design%20pattern/design-pattern-24-state-pattern/"><blockquote>
<p>您可於此 <a href="https://github.com/nickhuangcyh/design_pattern">design_pattern repo</a> 下載 Design Pattern 系列程式碼。</p>
</blockquote>
<hr />
<h2 id="需求">需求</h2>
<p>我們的任務是設計一個 <strong>飲水機</strong>,需求如下:</p>
<ul>
<li>飲水機有三種狀態:
<ul>
<li><strong>加熱中</strong>:提升水溫至熱水。</li>
<li><strong>冷卻中</strong>:降低水溫至冷水。</li>
<li><strong>待機中</strong>:維持現有水溫。</li>
</ul>
</li>
<li>使用者可透過按鈕切換飲水機的狀態。</li>
<li>飲水機需要根據當前狀態執行正確的行為,例如加熱狀態時加熱水,但不可冷卻。</li>
</ul>
<hr />
<h2 id="物件導向分析-ooa">物件導向分析 (OOA)</h2>
<p>理解需求後,讓我們來快速實作物件導向分析吧!</p>
<p><img src="/blog/assets/images/design_pattern_state_pattern_uml_1.png" alt="state_pattern_uml_1" /></p>
<h3 id="察覺-forces">察覺 Forces</h3>
<p>在未使用設計模式的情況下,我們可能面臨以下挑戰:</p>
<ol>
<li>
<p><strong>高耦合性 (High Coupling)</strong></p>
<ul>
<li>狀態邏輯與飲水機核心功能混合在一起,導致程式碼難以維護。</li>
</ul>
</li>
<li>
<p><strong>違反單一職責原則 (SRP)</strong></p>
<ul>
<li>飲水機類別需要同時處理狀態邏輯與主要功能,責任過於繁重。</li>
</ul>
</li>
<li>
<p><strong>難以擴展 (Hard to Extend)</strong></p>
<ul>
<li>新增或修改狀態行為需更改飲水機核心邏輯,違反開放關閉原則 (OCP)。</li>
</ul>
</li>
</ol>
<hr />
<h2 id="套用-state-pattern-solution-得到新的-context-resulting-context">套用 State Pattern (Solution) 得到新的 Context (Resulting Context)</h2>
<p>做完 OOA,察覺 Forces,看清楚整個 Context 後,就可以來套用 State Pattern 解決這個問題</p>
<p>察覺 Forces 後,我們可以套用 <strong>State Pattern</strong>,將狀態邏輯封裝成獨立的類別,達到以下效果:</p>
<p><img src="/blog/assets/images/design_pattern_state_pattern_uml_2.png" alt="state_pattern_uml_2" /></p>
<p>狀態模式有三個角色:</p>
<ol>
<li>
<p><strong>State (狀態介面)</strong><br />
定義所有具體狀態需要實現的行為。</p>
</li>
<li>
<p><strong>ConcreteState (具體狀態)</strong><br />
每個具體狀態類別實現 State 介面,並負責該狀態下的具體行為邏輯。</p>
</li>
<li>
<p><strong>Context (上下文)</strong><br />
負責維護當前狀態,並提供介面讓外部操作。在執行操作時,將請求委派給當前狀態物件。</p>
</li>
</ol>
<ul>
<li>飲水機類別負責狀態管理,而非具體行為實現,降低耦合度。</li>
<li>每個狀態專注於自身行為,符合單一職責原則。</li>
<li>新增或修改狀態無需影響飲水機核心邏輯,符合開放關閉原則。</li>
</ul>
<p>將 State Pattern 套用到我們的應用吧</p>
<p><img src="/blog/assets/images/design_pattern_state_pattern_uml_3.png" alt="state_pattern_uml_3" /></p>
<hr />
<h2 id="物件導向設計-oop">物件導向設計 (OOP)</h2>
<p>[State: WaterDispenserState]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">WaterDispenserState</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">handleRequest</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[ConcreteStates: HeatingState, CoolingState, StandbyState]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">HeatingState</span> <span class="p">:</span> <span class="nc">WaterDispenserState</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">handleRequest</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"加熱中:水溫正在提升,請稍候..."</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">CoolingState</span> <span class="p">:</span> <span class="nc">WaterDispenserState</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">handleRequest</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"冷卻中:水溫正在降低,請稍候..."</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="nc">StandbyState</span> <span class="p">:</span> <span class="nc">WaterDispenserState</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">handleRequest</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"待機中:飲水機維持現有水溫,隨時可用。"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Context: WaterDispenser]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">WaterDispenser</span> <span class="p">{</span>
<span class="k">private</span> <span class="kd">var</span> <span class="py">currentState</span><span class="p">:</span> <span class="nc">WaterDispenserState</span> <span class="p">=</span> <span class="nc">StandbyState</span><span class="p">()</span>
<span class="k">fun</span> <span class="nf">setState</span><span class="p">(</span><span class="n">state</span><span class="p">:</span> <span class="nc">WaterDispenserState</span><span class="p">)</span> <span class="p">{</span>
<span class="n">currentState</span> <span class="p">=</span> <span class="n">state</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"狀態切換:${state::class.simpleName}"</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">fun</span> <span class="nf">pressButton</span><span class="p">()</span> <span class="p">{</span>
<span class="n">currentState</span><span class="p">.</span><span class="nf">handleRequest</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Client]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">dispenser</span> <span class="p">=</span> <span class="nc">WaterDispenser</span><span class="p">()</span>
<span class="c1">// 初始狀態為待機中</span>
<span class="n">dispenser</span><span class="p">.</span><span class="nf">pressButton</span><span class="p">()</span>
<span class="c1">// 切換到加熱狀態</span>
<span class="n">dispenser</span><span class="p">.</span><span class="nf">setState</span><span class="p">(</span><span class="nc">HeatingState</span><span class="p">())</span>
<span class="n">dispenser</span><span class="p">.</span><span class="nf">pressButton</span><span class="p">()</span>
<span class="c1">// 切換到冷卻狀態</span>
<span class="n">dispenser</span><span class="p">.</span><span class="nf">setState</span><span class="p">(</span><span class="nc">CoolingState</span><span class="p">())</span>
<span class="n">dispenser</span><span class="p">.</span><span class="nf">pressButton</span><span class="p">()</span>
<span class="c1">// 回到待機狀態</span>
<span class="n">dispenser</span><span class="p">.</span><span class="nf">setState</span><span class="p">(</span><span class="nc">StandbyState</span><span class="p">())</span>
<span class="n">dispenser</span><span class="p">.</span><span class="nf">pressButton</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<p>[Output]</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">待機中:飲水機維持現有水溫,隨時可用。</span>
<span class="err">狀態切換:</span><span class="nc">HeatingState</span>
<span class="err">加熱中:水溫正在提升,請稍候</span><span class="o">..</span><span class="p">.</span>
<span class="err">狀態切換:</span><span class="nc">CoolingState</span>
<span class="err">冷卻中:水溫正在降低,請稍候</span><span class="o">..</span><span class="p">.</span>
<span class="err">狀態切換:</span><span class="nc">StandbyState</span>
<span class="err">待機中:飲水機維持現有水溫,隨時可用。</span>
</code></pre></div></div>