上一篇,我介紹了 Facade 模式,今天我們繼續介紹另一個設計模式 —— Adapter 模式。
假設客戶給出以下需求:
display
與 undisplay
行為的點、線、正方形分別建立類別;那麼,我們在這個時候會希望使用多型 —— 具有同樣的 method(display
, undisplay
),在不同物件會有不同的行為。但是客戶與他們的對話會是共同的。若以 UML 圖來看,會如同下圖:
這樣子,在我們的 Client 眼中,他互動的是 Shape
,不需要去知道自己有的是哪一種形狀。
今天,客戶需求增加了,客戶需要有個圓形,而且它也需要有 display
與 undisplay
兩種行為。
然而,你發現你同事在開發其他功能的時候,已經實作了一個名叫 MyCircle
類別,而此類別中剛好有 show
與 vanish
兩種 methods:他們所做的行為剛好就符合 display
和 undisplay
的需求。
可惜地是,這個 MyCircle
並沒有繼承於 Shape
,意指上述的架構無法套用在 MyCircle
上。
你當然可以假裝沒看到我的圓,直接實作出你自己的 Circle
類別,但是這樣等於是出現兩組類似的程式碼在你的系統裡。
GoF 如此開示:
(Adapter 模式)將一個類別的介面轉換成客戶希望的另一個介面。Adapter 模式使原本由於介面不相容而不能一起工作的類別可以一起工作。
意思就是,某類別的功能符合我們的需求,但是介面上不同於原本的設計,那麼我們就建立一個新介面重新包裝該類別。
那我們如何實作 Adapter 模式在上述的問題上呢?
其實很簡單,就是建立一個 Circle
物件,然後將 Circle
包含 MyCircle
。再將往後發自 Circle
的請求都丟給 MyCircle
去實作。
用 UML 圖來看,就會如下圖所示。
我們用 java 來實作,就會如下:
class Circle extends Shape {
...
private MyCircle myCircle;
...
public Circle() {
myCircle = new MyCircle();
}
void public display() {
myCircle.show();
}
void public undisplay() {
myCircle.vanish();
}
}
(終於有程式碼出現了,感動落淚)
如此一來,我們就能維持原本的架構:客戶不知道實際的形狀。Adapter 模式最常見的用途也就是在此 —— 維持多型。而有了 Adapter 模式,我們也不需要擔心原有類別的介面了,因為總是可以用 Adpter 模式來提供正確的介面。
項目 | 描述 |
---|---|
意圖 | 使控制範圍外的原有物件與某個介面匹配 |
問題 | 系統資料、行為皆正確,只有介面不符。常用於必須從抽象類別衍生時 |
解決方案 | 提供具有所需介面的 wrapper(包裝)類別 |
參與者與協作者 | Adapter 改變了 Adaptee 的介面,使 Adaptee 與 Adapter 的父類別 Target 匹配。如此 Client 就可以使用 Adaptee 了,好像它是 Target 類型。 |
效果 | 使原有物件能夠適應新的類別結構,不受介面的限制 |
實作 | 把原有類別包含在另一個類別中。讓包含類別與需要的介面匹配。呼叫被複合類別的方法 |
下圖為 Adapter 模式的 UML 圖:
實際上 Adapter 模式有兩種類型:
類別 Adapter 模式的實現方法是建立新類別,該類別同時從兩種類別繼承:
兩模式的差異如下表:
Facade 模式 | Adapter 模式 | |
---|---|---|
是否存在既有的類別? | 是 | 是 |
是否需要依某個介面設計? | 否 | 是 |
物件是否需要多型行為? | 否 | 可能 |
需要更簡單的見面嗎? | 是 | 否 |
簡言之,Facade 模式簡化了介面;Adapter 模式則將一個介面轉成另一個介面。
接下來 cover 的是第八章。我們將會再從介紹設計模式跳出來,再回頭討論一下物件導向設計,並且引進新的觀點。
雖然好像又有點偏離主題(設計模式),但我認為這是很精彩的一章,這一章讓我重新看待了很多原本在物件導向中習以為常的事。所以我還是希望花點篇幅討論一下。
那麼明天見囉!