適配器模式分為兩種:類適配器模式和對象適配器模式。 類適配器模式需要用到多重繼承機制(C++支持)。 然而Java/C#等語言不支持多重繼承,那麼可以採用對象適配器模式。 本文講解類適配器模式。 背景 我們有一個繪圖應用,可以在屏幕上繪製一些形狀。該應用首先會獲得該形狀占據的區域大小,然後將形狀繪製...
適配器模式分為兩種:類適配器模式和對象適配器模式。
類適配器模式需要用到多重繼承機制(C++支持)。
然而Java/C#等語言不支持多重繼承,那麼可以採用對象適配器模式。
本文首先講解類適配器模式在C++中的實現,然後講解對象適配器模式在Java中的實現。
背景
我們有一個繪圖應用,可以在屏幕上繪製一些形狀。該應用首先會獲得該形狀占據的區域大小,然後將形狀繪製在此區域內。
客戶端代碼:
int main(int argc, char** argv) { Shape* shape = new Shape(); //獲取該形狀占據的矩形區域,以便於確定在哪個區域繪製該形狀 Rect* boundingBox = shape->BoundingBox(); //繪製該形狀 /****/ return 0; }
現在我們引用了一個第三方庫,裡面有TextView類。
我們希望可以把TextView繪製在屏幕上,可是TextView類沒有BoundingBox方法,因此我們不知道TextView占據的區域形狀也就無法直接繪製該類。解決方案如下:
- 修改TextView類的代碼,以增加BoundingBox方法。(不可行,我們可能無法獲得源碼)
- 用適配器模式。(對,下麵我們來詳細講解)
適配器模式
適配器模式就像手機適配器(手機充電器)可以把220V的交流電轉換為手機可以直接使用的5V直流電。我們先介紹下適配器模式中的幾個術語:
- 目標類(Target):5V直流電
- 適配者(Adaptee): 220V交流電
- 適配器(Adapter):手機充電器
我們知道客戶端已經可以在屏幕上繪製Shape對象。然而不知道如何繪製TextView對象,因為它沒有BoundingBox方法。我們考慮新建一個TextShape類,其含有BoundingBox方法。
Shape是目標類(Target)。
TextView是適配者(Adaptee)。
TextShape是適配器(Adapter)。
(圖為類適配器模式)
(圖為對象適配器模式)
C++實現代碼(類適配器模式)
可以想象,適配器模式的關鍵是Adapter如何將Adaptee轉換為Targe。如何轉換取決於具體的應用。
Adapter代碼:
Rect* TextShape::BoundingBox() const { //一個中文字元占用2個相對寬度 //一個英文字元占用1個相對寬度 //這裡為了簡單起見,一律認為是1個相對寬度 float width = this->GetText().length(); float heigh = 1.0; //高度和寬度都要乘上字體大小比例 return new Rect( new Point(0, 0), new Point(width * this->GetFontSize(), heigh * this->GetFontSize()) ); }
客戶端代碼:
//採用類適配器模式的話,客戶端代碼會比較容易 //如果採用對象適配器模式,客戶端的代碼會多一點 int main() { Shape* shape = new TextShape(); Rect* boundingBox = shape->BoundingBox(); return 0; }
Java實現代碼(對象適配器模式)
適配器代碼:
//由於不支持多繼承 //需要在構造函數傳入adaptee對象 public class TextShape extends Shape{ private TextView textView; public TextShape(TextView textView) { this.textView = textView; } @Override public Rectangle boundingBox() { //根據textView的內容計算占據的區域大小 //這裡需要較複雜的代碼 return null; } }
客戶端代碼:
public class Client { public static void main(String[] args) { TextView textView = new TextView(); Shape shape = new TextShape(textView); shape.boundingBox(); } }