適配器模式,即根據客戶端需要,將某個類的接口轉換成特定樣式的接口,以解決類之間的兼容問題。
如果我們的代碼依賴一些外部的API,或者依賴一些可能會經常更改的類,那麼應該考慮用適配器模式。
下面我們以集成支付寶支付功能爲例。
1 問題
假設支付寶支付類的功能如下:
/**
* 支付寶支付類
*/
class Alipay
{
public function sendPayment()
{
echo '使用支付寶支付。';
}
}
// 客戶端代碼
$alipay = new Alipay();
$alipay->sendPayment();
我們直接實例化Alipay
類完成支付功能,這樣的客戶端代碼可能很多。
一段時間後,如果支付寶的Alipay
類升級,方法名由sendPayment()
變成goPayment()
會怎樣?
所有用了sendPayment()
的客戶端代碼都要改變。
如果Alipay
類頻繁升級,或者客戶端在很多地方使用,這會是極大的工作量。
2 解決
現在我們用適配器模式來解決。
我們在客戶端和Alipay
類之間加一箇中間類,也就是適配器類,轉換原始的Alipay
爲客戶端需要的形式。
爲讓客戶端能調用到統一的類方法,我們先定義一個適配器接口:
/**
* 適配器接口,所有的支付適配器都需實現這個接口。
* 不管第三方支付實現方式如何,對於客戶端來說,都
* 用pay()方法完成支付
*/
interface PayAdapter
{
public function pay();
}
因爲Alipay
類我們無法控制,而且它有可能經常更新,所以我們不對它做任何修改。
我們新建一個AlipayAdapter
適配器類,在pay()
中轉換Alipay
的支付功能,如下:
/**
* 支付寶適配器
*/
class AlipayAdapter implements PayAdapter
{
public function pay()
{
// 實例化Alipay類,並用Alipay的方法實現支付
$alipay = new Alipay();
$alipay->sendPayment();
}
}
客戶端使用方式:
// 客戶端代碼
$alipay = new AlipayAdapter();
// 用pay()方法實現支付
$alipay->pay();
這樣,當Alipay
的支付方法改變,只需要修改AlipayAdapter
類就可以了。
3 適配新類
有了適配器後,擴展也變得更容易了。
繼續以上的例子,在支付寶的基礎上,我們再增加微信支付,它與支付寶的支付方式不同,必須通過掃碼才能支付。
這種情況也應該使用適配器,而不是直接使用微信的支付功能。
代碼如下:
/**
* 微信支付類
*/
class WechatPay
{
public function scan()
{
echo '掃描二維碼後,';
}
public function doPay()
{
echo '使用微信支付';
}
}
/**
* 微信支付適配器
*/
class WechatPayAdapter implements PayAdapter
{
public function pay()
{
// 實例化WechatPay類,並用WechatPay的方法實現支付。
// 注意,微信支付的方式和支付寶的支付方式不一樣,但是
// 適配之後,他們都能用pay()來實現支付功能。
$wechatPay = new WechatPay();
$wechatPay->scan();
$wechatPay->doPay();
}
}
客戶端使用:
// 客戶端代碼
$wechat = new WechatPayAdapter();
// 也是用pay()方法實現支付
$wechat->pay();
這就是適配器的擴展特性。
我們創建了一個用於處理第三方類(支付寶、微信支付)的方法,
如果它們的API有變化,我們僅需修改客戶端依賴的適配器類就可以,不用修改、暴露第三方類本身。
4 UML圖
以上適配器模式的代碼對應UML如下:
注意:適配器模式中,適配器類的名稱和創建方式一定是不會頻繁改動的。
對於客戶端來說,引用適配器類的方式應該是統一而不變的,這纔算是正確使用適配器。
5 總結
大的應用都會不斷地加入新庫和新API。
爲避免它們的變更引發問題,應該用適配器模式包裝起來,提供應用統一的引用方式。
它會讓我們的代碼更具結構化,便於管理和擴展。
參考資料: