以實際範例一步步重構成 Adapter Pattern

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。

adapter000

adapter010

  • 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

adapter001

我的 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)

adapter002

下載檔案
adapter002

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
{

/** @var CloudStorageService */
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
/** @var CloudStorageService */
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() 呼叫 CloudStorageServicegetFileUrl() 回傳下載檔案的 url。

28 行

1
2
3
4
5
6
7
/**
* 上傳檔案
*/

public function store()
{

$this->cloudStorageService->uploadFile('MyFolder', 'MyRemoteFile', 'MyLocalFile');
}

store() 呼叫 CloudStorageServiceuploadFile() 上傳檔案。

36 行

1
2
3
4
5
6
7
/**
* 刪除檔案
*/

public function destroy()
{

$this->cloudStorageService->deleteFile('MyFolder', 'MyRemote');
}

destroy() 呼叫 CloudStorageServicedeleteFile() 刪除檔案。

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
{

/** @var AWSSDK */
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
/** @var AWSSDK */
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);
}

呼叫 AWSSDKputObject() 上傳檔案。

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);
}

呼叫 AWSSDKgetObject() 下載檔案。

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);
}

呼叫 AWSSDKdeleteObject() 刪除檔案。

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');
}

模擬 AWSSDKputObject()

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';
}

模擬 AWSSDKgetObject()

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');
}

模擬 AWSSDKdeleteObject()

使用 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 去判斷該使用 AWSSDKAzureSDK,因為 CloudStorageService 應該封閉,應該新增 class,改用物件導向多型取代 if else

由於 CloudStorageService 原本已經使用 AWSSDK,為了讓 CloudStorageService 封閉不做修改,我們決定以既有的 AWSSDK 為基礎加以 extract interface 產生 CloudSDK interface 。

adapter003

下載檔案

adapter012

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
{

/** @var CloudSDK */
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 行

1
2
/** @var CloudSDK */
private $cloudSDK;

透過重構的 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
{

/** @var AWSSDK */
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
{

/** @var AzureSDK */
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
/** @var AzureSDK */
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 所定義,所以一定要實作,轉而呼叫 AzureSDKcreateBlob()

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 所定義,所以一定要實作,轉而呼叫 AzureSDKgetObjetUrl()

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 所定義,所以一定要實作,轉而呼叫 AzureSDKdeleteObject()

雖然 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 而言,儘管 AWSSDKAzureSDK 的 API 都不一樣,但已經將兩者抽象化為 CloudSDK,對 service 而言都是一樣的 SDK,只要透過 AWSAdapterAzureAdapter 作轉換即可。

就類似僅管現在投影機有 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 (對修改是封閉的)。

adapter004

上傳檔案

adapter013

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
{

/** @var AliyunSDK*/
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

adapter005

實務上 Adapter pattern 都會搭配一個 Simple Factory pattern,由 factory 決定要使用哪一個 adapter。

Adapter 的變形

adapter006

adapter014

Adapter 不限於只能搭配單一 Adaptee,實務上可以搭配多個 Adaptee,只要 Target interface 保持穩定即可。

Conclusion

  • 資料結構相對於 C 語言的指標,就相當於設計模式相對於 interface;想學怎麼活用指標,就要去學資料結構,想學怎麼活用 interface,就要去學設計模式
  • Strategy + Simple Factory (依賴注入)Adapter + Simple Factory (依賴注入) 是實務上最常用到的物件導向,學會這兩招,幾乎學會了 60% 的物件導向。
  • 若會善用物件導向,程式碼的 if else 數量會降到最低,循環複雜度也會降到最低,會將原來該用 if else 改用 interface,用很多小檔案取代很長的檔案
  • 設計模式都會有些變形,不用在乎每本書講的設計模式都不太一樣,設計模式只重其意,不重其招,重點是搞懂設計模式所要解決問題的本質,就可以自行加以變形,靈活運用。

adapter007

adapter008

adapter009

Sample Code


完整的範例可以在我的 GitHub 上找到。