Adapter pattern 是實務上常用的設計模式,本文將以實際案例,根據需求一步步重構,最後變成 adapter pattern。
Version
PHP 7.0.15
定義
Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise of incompatible interfaces.
將一個 class 的接口變成使用端所期待的另外一種接口,從而使原本因接口不匹配而無法在一起工作的兩個 class 都能在一起工作。
白話就是
當 service 原本支援某一 API ,卻被要求支援另一新 API,可開發 adapter 讓原 service 支援新 API。
Client : 使用端根據 Target
interface 使用 Adapter
物件。
Controller 根據 service 的 interface 去使用 adapter。
Target : 根據使用端的 domain 需求定義出的 service 的 Target
interface。
Service 根據 controller 的需求,定義出 service 的 interface。
Adapter : 根據 Target
interface 實作出 Adapter
,再由 Adapter
去使用 Adaptee
。
根據 service 的 interface 實作出 adapter,再由 adapter 去使用實際的第三方套件或 API。
Adaptee : 被 Adapter
呼叫的 Adaptee
。
實際的第三方套件或 API。
生活中的 Adapter
Adapter 在生活中到處可見。
VGA Adapter
我的 Macbook Pro Retina 只有 Mini Display port,但投影機只有 VGA port,因此 Macbook 無法直接使用投影機,需要 VGA adapter 做轉接。
我的 controller 只支援 Mini Display port interface,但第三方套件或 API 卻只提供 VGA interface,因此 controller 無法直接使用第三方套件或 API,因此需要 adapter 做轉接,才能使用第三方套件或 API。
若以 Adapter pattern 思考
Client
就類似 Macbook,Adaptee
就類似投影機,這些都是不能修改的,我們只好以 MacBook 角度訂出 Target
interface,然後實做 VGA Adapter 實作 Target
interface,這樣 MacBook 就能使用投影機了。
真實案例
使用 AWS S3 AWS 提供 S3 (Simple Storage Service) 服務,讓我們可以上傳檔案到雲端,並且下載。AWS 也提供了 AWS SDK for PHP
供 PHP 使用。
AWS 的 API 如下 (AWSSDK
)
功能描述
API
上傳檔案
putObject($container, $blob, $file)
下載連結
getObjectUrl($container, $blob)
刪除檔案
deleteObjet($container, $blob)
下載檔案
CloudStorageController
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 namespace App \Http \Controllers ;use App \Services \CloudStorageService ;class CloudStorageController extends Controller { private $cloudStorageService ; * CloudStorageController constructor. * @param CloudStorageService $cloudStorageService */ public function __construct (CloudStorageService $cloudStorageService ) { $this ->cloudStorageService = $cloudStorageService ; } * 下載檔案 */ public function index () { $url = $this ->cloudStorageService->getFileUrl('MyFolder' , 'MyRemoteFile' ); echo ($url ); } * 上傳檔案 */ public function store () { $this ->cloudStorageService->uploadFile('MyFolder' , 'MyRemoteFile' , 'MyLocalFile' ); } * 刪除檔案 */ public function destroy () { $this ->cloudStorageService->deleteFile('MyFolder' , 'MyRemote' ); } }
第 9 行
1 2 3 4 5 6 7 8 9 10 11 private $cloudStorageService ; * CloudStorageController constructor. * @param CloudStorageService $cloudStorageService */ public function __construct (CloudStorageService $cloudStorageService ) { $this ->cloudStorageService = $cloudStorageService ; }
CloudStorageController
使用到了 CloudStorageService
,在 constructor 使用依賴注入將 CloudStorageService
注入。
19 行
1 2 3 4 5 6 7 8 * 下載檔案 */ public function index () { $url = $this ->cloudStorageService->getFileUrl('MyFolder' , 'MyRemoteFile' ); echo ($url ); }
index()
呼叫 CloudStorageService
的 getFileUrl()
回傳下載檔案的 url。
28 行
1 2 3 4 5 6 7 * 上傳檔案 */ public function store () { $this ->cloudStorageService->uploadFile('MyFolder' , 'MyRemoteFile' , 'MyLocalFile' ); }
store()
呼叫 CloudStorageService
的 uploadFile()
上傳檔案。
36 行
1 2 3 4 5 6 7 * 刪除檔案 */ public function destroy () { $this ->cloudStorageService->deleteFile('MyFolder' , 'MyRemote' ); }
destroy()
呼叫 CloudStorageService
的 deleteFile()
刪除檔案。
CloudStorageService
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 namespace App \Services ;use App \SDK \AWSSDK ;class CloudStorageService { private $awsSDK ; * CloudStorageService constructor. */ public function __construct () { $this ->awsSDK = new AWSSDK(); } * 上傳檔案 * @param string $folder * @param string $remoteFile * @param string $localFile */ public function uploadFile (string $folder , string $remoteFile , string $localFile ) { $this ->awsSDK->putObject($folder , $remoteFile , $localFile ); } * 下載檔案 * @param string $folder * @param string $remoteFile * @return string */ public function getFileUrl (string $folder , string $remoteFile ) { return $this ->awsSDK->getObject($folder , $remoteFile ); } * 刪除檔案 * @param string $folder * @param string $remoteFile */ public function deleteFile (string $folder , string $remoteFile ) { $this ->awsSDK->deleteObject($folder , $remoteFile ); } }
實際的提供雲端檔案服務的 service。
第 10 行
1 2 3 4 5 6 7 8 9 10 private $awsSDK ; * CloudStorageService constructor. */ public function __construct () { $this ->awsSDK = new AWSSDK(); }
在 CloudStorageService
使用到了 AWSSDK
。
這裡可以使用依賴注入,不過依賴注入就得搭配 provider 機制,這裡故意使用 new
,將來要搭配 Factory Pattern。
18 行
1 2 3 4 5 6 7 8 9 10 * 上傳檔案 * @param string $folder * @param string $remoteFile * @param string $localFile */ public function uploadFile (string $folder , string $remoteFile , string $localFile ) { $this ->awsSDK->putObject($folder , $remoteFile , $localFile ); }
呼叫 AWSSDK
的 putObject()
上傳檔案。
29 行
1 2 3 4 5 6 7 8 9 10 * 下載檔案 * @param string $folder * @param string $remoteFile * @return string */ public function getFileUrl (string $folder , string $remoteFile ) { return $this ->awsSDK->getObject($folder , $remoteFile ); }
呼叫 AWSSDK
的 getObject()
下載檔案。
40 行
1 2 3 4 5 6 7 8 9 * 刪除檔案 * @param string $folder * @param string $remoteFile */ public function deleteFile (string $folder , string $remoteFile ) { $this ->awsSDK->deleteObject($folder , $remoteFile ); }
呼叫 AWSSDK
的 deleteObject()
刪除檔案。
AWSSDK
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 namespace App \SDK ;class AWSSDK { * 上傳檔案 * @param string $container * @param string $blob * @param string $file */ public function putObject (string $container , string $blob , string $file ) { echo ('AWS S3 uploading file' ); } * 回傳下載檔案 url * @param string $container * @param string $blob * @return string */ public function getObject (string $container , string $blob ) : string { return 'http://www.aws.com' ; } * 刪除檔案 * @param string $container * @param string $blob */ public function deleteObject (string $container , string $blob ) { echo ('AWS S3 deleting file' ); } }
第 5 行
1 2 3 4 5 6 7 8 9 10 * 上傳檔案 * @param string $container * @param string $blob * @param string $file */ public function putObject (string $container , string $blob , string $file ) { echo ('AWS S3 uploading file' ); }
模擬 AWSSDK
的 putObject()
。
16 行
1 2 3 4 5 6 7 8 9 10 * 回傳下載檔案 url * @param string $container * @param string $blob * @return string */ public function getObject (string $container , string $blob ) : string { return 'http://www.aws.com' ; }
模擬 AWSSDK
的 getObject()
。
27 行
1 2 3 4 5 6 7 8 9 * 刪除檔案 * @param string $container * @param string $blob */ public function deleteObject (string $container , string $blob ) { echo ('AWS S3 deleting file' ); }
模擬 AWSSDK
的 deleteObject()
。
使用 Azure Blob Storage Microsof 的 Azure 雲端服務急起直追,且提供比 AWS 更優惠方案,公司高層為了降低成本,決定將檔案改放到 Azure 。
Azure 提供 Blob Storage 服務,功能與 S3 完全一樣,Microsoft 也提供了 Azure SDK for PHP
供 PHP 使用。
公司另有但書,先不要將原本 AWS S3 的程式碼刪除,因為有可能 Azure 便宜但不好用,將來有可能會回來用 AWS S3,希望提供設定檔 ,能動態切換使用 AWS S3 或 Azure Blob。
Azure 的 API 如下 (AzureSDK
)
功能描述
API
上傳檔案
createBlob($container, $blob, $file)
下載連結
getBlobUrl($container, $blob)
刪除檔案
deleteBlob($container, $blob)
根據開放封閉原則 ,AzureSDK
屬於新的需求,我們可以新增 class 支援 AzureSDK
(對擴展是開放的 ),但不應該去修改原有CloudStorageService
(對修改是封閉的 )。
我們不應該在 CloudStorageService 使用 if else
去判斷該使用 AWSSDK
或 AzureSDK
,因為 CloudStorageService
應該封閉,應該新增 class,改用物件導向多型 取代 if else
。
由於 CloudStorageService
原本已經使用 AWSSDK
,為了讓 CloudStorageService
封閉不做修改,我們決定以既有的 AWSSDK
為基礎加以 extract interface
產生 CloudSDK
interface 。
下載檔案
CloudSDK
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 namespace App \Services ;interface CloudSDK { * 上傳檔案 * @param string $container * @param string $blob * @param string $file */ public function pubObject (string $container , string $blob , string $file ) ; * 回傳下載檔案 url * @param string $container * @param string $blob * @return string */ public function getObjectUrl (string $container , string $blob ) ; * 刪除檔案 * @param string $container * @param string $blob */ public function deleteObject (string $container , string $blob ) ; }
使用 PhpStorm,從 AWSSDK
抽出 CloudSDK
interface。
為了讓 CloudStorageService
封閉不要修改,我們希望 CloudStorageService
使用的 SDK 都能遵守 CloudSDK
interface,如此 CloudStorageService
就能完全不用修改。
CloudStorageService
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 namespace App \Services ;class CloudStorageService { private $cloudSDK ; * CloudStorageService constructor. */ public function __construct () { $this ->cloudSDK = CloudSDKFactory::create(); } * 上傳檔案 * @param string $folder * @param string $remoteFile * @param string $localFile */ public function uploadFile (string $folder , string $remoteFile , string $localFile ) { $this ->cloudSDK->pubObject($folder , $remoteFile , $localFile ); } * 下載檔案 * @param string $folder * @param string $remoteFile * @return string */ public function getFileUrl (string $folder , string $remoteFile ) { return $this ->cloudSDK->getObjectUrl($folder , $remoteFile ); } * 刪除檔案 * @param string $folder * @param string $remoteFile */ public function deleteFile (string $folder , string $remoteFile ) { $this ->cloudSDK->deleteObject($folder , $remoteFile ); } }
第 5 行
透過重構的 Rename
,將 $awsSDK
重構成 $cloudSDK
,並將型別改成 CloudSDK
。
8 行
1 2 3 4 5 6 7 * CloudStorageService constructor. */ public function __construct () { $this ->cloudSDK = CloudSDKFactory::create(); }
將 new AWSSDK
改成 CloudSDKFactory::create()
,改由工廠 來建立物件。
開放封閉原則 規定我們不要修改 CloudStorageService
,但 constructor 的修改是可接受的,但 method 內則不應該修改。
至於 CloudStorageService
的其他部分則不修改,以達成開放封閉原則 的要求。
AWSAdapter
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 namespace App \Services ;use App \SDK \AWSSDK ;class AWSAdapter implements CloudSDK { private $awsSDK ; * AWSAdapter constructor. * @param AWSSDK $awsSDK */ public function __construct (AWSSDK $awsSDK ) { $this ->awsSDK = $awsSDK ; } * 上傳檔案 * @param string $container * @param string $blob * @param string $file */ public function pubObject (string $container , string $blob , string $file ) { $this ->awsSDK->putObject($container , $blob , $file ); } * 回傳下載檔案 url * @param string $container * @param string $blob * @return string */ public function getObjectUrl (string $container , string $blob ) { return $this ->awsSDK->getObject($container , $blob ); } * 刪除檔案 * @param string $container * @param string $blob */ public function deleteObject (string $container , string $blob ) { $this ->awsSDK->deleteObject($container , $blob ); } }
第 5 行
1 class AWSAdapter implements CloudSDK
因為 CloudSDK
interface 已經確定,因此我們需要一個 adapter 將 CloudSDK
interface 轉成各 SDK 所定義的 interface。
由於 CloudSDK
是從 AWSSDK
抽出來的,因此 interface 完全一樣,AWSAdapter
只是將相同的 method 導到 AWSSDK
而已。
這裡因為 CloudSDK
interface 與 AWSSDK
完全一樣,所以暫時看不到 CloudSDK
interface 的威力,稍後才會展現。
AzureAdapter
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 namespace App \Services ;use App \SDK \AzureSDK ;class AzureAdapter implements CloudSDK { private $azureSDK ; * AzureAdapter constructor. * @param AzureSDK $azureSDK */ public function __construct (AzureSDK $azureSDK ) { $this ->azureSDK = $azureSDK ; } * 上傳檔案 * @param string $container * @param string $blob * @param string $file */ public function pubObject (string $container , string $blob , string $file ) { $this ->azureSDK->createBlob($container , $blob , $file ); } * 回傳下載檔案 url * @param string $container * @param string $blob * @return string */ public function getObjectUrl (string $container , string $blob ) { return $this ->getObjectUrl($container , $blob ); } * 刪除檔案 * @param string $container * @param string $blob */ public function deleteObject (string $container , string $blob ) { $this ->deleteObject($container , $blob ); } }
第 5 行
1 class AzureAdapter implements CloudSDK
AzureAdapter
也實現 CloudSDK
interface。
第 7 行
1 2 3 4 5 6 7 8 9 10 11 private $azureSDK ; * AzureAdapter constructor. * @param AzureSDK $azureSDK */ public function __construct (AzureSDK $azureSDK ) { $this ->azureSDK = $azureSDK ; }
依賴注入進 AzureSDK
。
19 行
1 2 3 4 5 6 7 8 9 10 * 上傳檔案 * @param string $container * @param string $blob * @param string $file */ public function pubObject (string $container , string $blob , string $file ) { $this ->azureSDK->createBlob($container , $blob , $file ); }
putObject()
為 CloudSDK
interface 所定義,所以一定要實作,轉而呼叫 AzureSDK
的 createBlob()
。
30 行
1 2 3 4 5 6 7 8 9 10 * 回傳下載檔案 url * @param string $container * @param string $blob * @return string */ public function getObjectUrl (string $container , string $blob ) { return $this ->getObjectUrl($container , $blob ); }
getObjectUrl()
為 CloudSDK
interface 所定義,所以一定要實作,轉而呼叫 AzureSDK
的 getObjetUrl()
。
41 行
1 2 3 4 5 6 7 8 9 * 刪除檔案 * @param string $container * @param string $blob */ public function deleteObject (string $container , string $blob ) { $this ->deleteObject($container , $blob ); }
deleteObject()
為 CloudSDK
interface 所定義,所以一定要實作,轉而呼叫 AzureSDK
的 deleteObject()
。
雖然 adapter 轉呼叫 AzureSDK
看似多餘,但由於實作了 CloudSDK
interface,使得 CloudStorageService
不用修改就可以使用 AzureSDK
,就類似 Macbook 不用改裝 VGA 介面,透過 adapter 就可以由 Mini Display 去使用 VGA 介面的投影機一樣,達成開放封閉原則 的要求。
CloudSDKFactory
1 2 3 4 5 6 7 8 9 namespace App \Services ;class CloudSDKFactory { public static function create () : CloudSDK { } }
CloudSDKFactory
暫時只回傳 CloudSDK
interface,至於要如何建立物件,我們最後再來實做。
AzureSDK
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 namespace App \SDK ;class AzureSDK { * 上傳檔案 * @param string $container * @param string $blob * @param string $file */ public function createBlob (string $container , string $blob , string $file ) { echo ('Azure Blob uploading file' ); } * 回傳下載檔案 url * @param string $container * @param string $blob * @return string */ public function getBlobUrl (string $container , string $blob ) : string { return 'http://www.azure.com' ; } * 刪除檔案 * @param string $container * @param string $blob */ public function deleteBlob (string $container , string $blob ) { echo ('Azure Blob deleting file' ); } }
模擬 AzureSDK
的 API。
對 CloudStorageService
而言,儘管 AWSSDK
與 AzureSDK
的 API 都不一樣,但已經將兩者抽象化為 CloudSDK
,對 service 而言都是一樣的 SDK,只要透過 AWSAdapter
與 AzureAdapter
作轉換即可。
就類似僅管現在投影機有 VGA 也有 HDMI,對於 Macbook 而言都是 Mini Display Port,只要提供 VGA adapter 或 HDMI adapter 即可。
使用阿里雲 OSS 雖然 Azure 提供很便宜的價格,但公司又擔心川普 上台後,放在 Azure 的資料會被中國的網路長城 封鎖,導致中國無法存取 Azure 檔案,因此高層決定將同一份程式,分別放到阿里雲與 Azure,相同網址,若在中國就使用阿里雲,其他國家使用 Azure。
阿里雲提供 OSS (Open Storage Service) 服務,功能與 AWS S3 與 Azure Blob 完全一樣,阿里雲也提供了 Aliyun SDK for PHP
供 PHP 使用。
阿里雲的 API 如下 (AliyunSDK
)
功能描述
API
上傳檔案
setBucket($container); uploadFile($blob, $file)
下載連結
setBucket($container); getUrl($blob)
刪除檔案
deleteObjet($container, $blob)
目前 Azure 與阿里雲必須並存,AWS 暫時不用,但不能刪除,根據開放封閉原則 ,AliyunSDK
屬於新的需求,我們可以新增 class 支援 AliyunSDK
(對擴展是開放的 ),但不應該去修改原有 CloudStorageService
(對修改是封閉的 )。
上傳檔案
AliyunAdapter
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 namespace App \Services ;use App \SDK \AliyunSDK ;class AliyunAdapter implements CloudSDK { private $aliyunSDK ; * AliyunAdapter constructor. * @param AliyunSDK $aliyunSDK */ public function __construct (AliyunSDK $aliyunSDK ) { $this ->aliyunSDK = $aliyunSDK ; } * 上傳檔案 * @param string $container * @param string $blob * @param string $file */ public function pubObject (string $container , string $blob , string $file ) { $this ->aliyunSDK->setBucket($container ); $this ->aliyunSDK->uploadFile($blob , $file ); } * 回傳下載檔案 url * @param string $container * @param string $blob * @return string */ public function getObjectUrl (string $container , string $blob ) { $this ->aliyunSDK->setBucket($container ); return $this ->aliyunSDK->getUrl($blob ); } * 刪除檔案 * @param string $container * @param string $blob */ public function deleteObject (string $container , string $blob ) { $this ->aliyunSDK->deleteObject($container , $blob ); } }
19 行
1 2 3 4 5 6 7 8 9 10 11 * 上傳檔案 * @param string $container * @param string $blob * @param string $file */ public function pubObject (string $container , string $blob , string $file ) { $this ->aliyunSDK->setBucket($container ); $this ->aliyunSDK->uploadFile($blob , $file ); }
putObject()
為 CloudSDK
interface 所定義,所以一定要實作,因為 AliyunSDK
的 API 分兩步驟,需要先 setBucket()
,然後再 uploadFile()
。
31 行
1 2 3 4 5 6 7 8 9 10 11 * 回傳下載檔案 url * @param string $container * @param string $blob * @return string */ public function getObjectUrl (string $container , string $blob ) { $this ->aliyunSDK->setBucket($container ); return $this ->aliyunSDK->getUrl($blob ); }
getObjectUrl()
為 CloudSDK
interface 所定義,所以一定要實作,因為 AliyunSDK
的 API 分兩步驟,需要先 setBucket()
,然後再 getUrl()
。
CloudFactory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 namespace App \Services ;class CloudSDKFactory { * 建立 adapter * @return CloudSDK */ public static function create () : CloudSDK { $lut = config('app.CloudLUT' ); $cloudStorage = config('app.CloudStorage' ); $className = collect($lut ) ->get($cloudStorage , AWSAdapter::class); return new $className ; } }
$lut
為工廠的對照表,紀錄什麼 key 該對應哪個 adapter。
$cloudStorage
為目前該使用什麼 SDK,key 為 AWS、Azure 還是 Aliyun。
使用 Collection 根據 key 去抓 value,決定該 new 什麼 adapter。
平常 service 不應該使用 static
,但工廠是可用 static
的。
config/app.php
1 2 3 4 5 6 7 'CloudLUT' => [ 'AWS' => AWSAdapter::class, 'Azure' => AzureAdapter::class, 'Aliyun' => AliyunAdapter::class, ], 'CloudStorage' => 'Azure' ,
app.php
為設定檔 。
CloudLUT
為 key / value 對照表,決定要 new 什麼 adapter。
CloudStorage
決定目前主機要使用什麼雲端服務,這個檔案可以不放在 git,由 DevOps 去維護。
開放封閉原則
對於擴展是開放的,對於修改是封閉的。
白話就是
若有新的需求,可以增加程式碼,而不應該修改既有的程式碼。
開了 CloudSDK
interface 之後,之後雖然新增了 AliyunSDK
需求,我們只需要:
新增 AliyunAdapter
class。
新增 app.php
設定檔的 CloudLUT
。
其他原本的程式碼完全沒修改。
整個程式碼沒看到一行的 if else
去切換 SDK,全部用物件導向的多型 就可達成。
Interface 之前的 controller 與 service 保持封閉 ,interface 之後的 adapter 保持開放 ,達成開放封閉原則 的要求。
實務上的 Adapter 我們可以發現使用 Adapter pattern 後,由於訂出 Target
interface,可以針對不同的 Adaptee
擴充。
但所有設計模式都面臨一個最基本的問題 :由誰決定根據 interface 所建立的物件 ,也就是該由誰決定 Adapter
。
實務上 Adapter pattern 都會搭配一個 Simple Factory pattern,由 factory 決定要使用哪一個 adapter。
Adapter 的變形
Adapter 不限於只能搭配單一 Adaptee,實務上可以搭配多個 Adaptee,只要 Target interface 保持穩定即可。
Conclusion
資料結構 相對於 C 語言的指標 ,就相當於設計模式 相對於 interface ;想學怎麼活用指標 ,就要去學資料結構 ,想學怎麼活用 interface ,就要去學設計模式 。
Strategy + Simple Factory (依賴注入) 或 Adapter + Simple Factory (依賴注入) 是實務上最常 用到的物件導向,學會這兩招,幾乎學會了 60% 的物件導向。
若會善用物件導向,程式碼的 if else
數量會降到最低,循環複雜度 也會降到最低,會將原來該用 if else
改用 interface
,用很多小檔案 取代很長的檔案 。
設計模式都會有些變形,不用在乎每本書講的設計模式都不太一樣,設計模式只重其意,不重其招 ,重點是搞懂設計模式所要解決問題的本質,就可以自行加以變形,靈活運用。
Sample Code
完整的範例可以在我的 GitHub 上找到。