我的設計模式之旅。本節學習了適配器模式。從程式調用第三方庫時遇到的問題著手,思考如何讓兩個不關聯的類一起工作。並嘗試使用C#運用適配器模式解決方釘與圓孔問題。 ...
編程旅途是漫長遙遠的,在不同時刻有不同的感悟,本文會一直更新下去。
思考總結
思考問題
程式調用第三方庫經常會遇到的問題?
你可能根本沒有程式庫的源代碼,從而無法對其進行修改。
什麼是適配器模式
適配器是一種結構型設計模式, 它能使介面不相容的對象能相互合作。
適配器模式:將一個類的介面轉換成客戶希望的另外一個介面。使得原本由於介面不相容而不能和一起工作的那些類可以一起工作。
含義:
- 適配器模式通過封裝對象將複雜的轉換過程隱藏於幕後。被封裝的對象甚至察覺不到適配器的存在。
- 適配器不僅可以轉換不同格式的數據,其還有助於採用不同介面的對象之間的合作。
工作方式:
- 適配器實現與其中一個現有對象相容的介面。
- 現有對象可以使用該介面安全地調用適配器方法。
- 適配器方法被調用後將以另一個對象相容的格式和順序將請求傳遞給該對象。有時你甚至可以創建一個雙向適配器來實現雙向轉換調用。
何時使用:
- 當你希望使用某個類,但是其介面與其他代碼不相容時,可以使用適配器類。
- 適配器模式允許你創建一個中間層類,其可作為代碼與遺留類、第三方類或提供怪異介面的類之間的轉換器。
- 如果您需要復用這樣一些類,他們處於同一個繼承體系,並且他們又有了額外的一些共同的方法,但是這些共同的方法不是所有在這一繼承體系中的子類所具有的共性。將缺失功能添加到一個適配器類中是一種優雅得多的解決方案。這種方式同裝飾模式非常相似。
- 通過介面轉換,將一個類插入另一個類系中。(比如老虎和飛禽,現在多了一個飛虎,在不增加實體的需求下,增加一個適配器,在裡面包容一個虎對象,實現飛的介面。)
實現方式:
- 確保至少有兩個類的介面不相容。
- 一個無法修改(通常是第三方、遺留系統或者存在眾多已有依賴的類)的功能性服務類。
- 一個或多個將受益於使用服務類的客戶端類。
- 聲明客戶端介面,描述客戶端如何與服務交互。
- 創建遵循客戶端介面的適配器類。所有方法暫時都為空。
- 在適配器類中添加一個成員變數用於保存對於服務對象的引用。 通常情況下會通過構造函數對該成員變數進行初始化, 但有時在調用其方法時將該變數傳遞給適配器會更方便。
- 依次實現適配器類客戶端介面的所有方法。適配器會將實際工作委派給服務對象,自身只負責介面或數據格式的轉換。
- 客戶端必須通過客戶端介面使用適配器。這樣一來,你就可以在不影響客戶端代碼的情況下修改或擴展適配器。
應用實例:
- 美國電器 110V,中國 220V,就要有一個適配器將 110V 轉化為 220V。
- 在 LINUX 上運行 WINDOWS 程式。
優點:
- 可以讓任何兩個沒有關聯的類一起運行。
- 提高了類的復用。 靈活性好。
- 單一職責原則你可以將介面或數據轉換代碼從程式主要業務邏輯中分離。
- 開閉原則。客戶端代碼通過客戶端介面與適配器進行交互,你就能在不修改現有客戶端代碼的情況下在程式中添加新類型的適配器。
缺點:
- 代碼整體複雜度增加,因為你需要新增一系列介面和類。過多地使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是 A 介面,其實內部被適配成了 B 介面的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。
註意事項:
- 適配器不是在詳細設計時添加的,而是解決正在服役的項目的問題。
與其他模式的關係:
- 橋接通常會於開發前期進行設計,使你能夠將程式的各個部分獨立開來以便開發。另一方面,適配器通常在已有程式中使用,讓相互不相容的類能很好地合作。
- 適配器可以對已有對象的介面進行修改,裝飾能在不改變對象介面的前提下強化對象功能。此外,裝飾還支持遞歸組合,適配器則無法實現。
- 適配器能為被封裝對象提供不同的介面,代理能為對象提供相同的介面,裝飾則能為對象提供加強的介面。
- 外觀為現有對象定義了一個新介面,適配器則會試圖運用已有的介面。適配器通常只封裝一個對象,外觀通常會作用於整個對象子系統上。
- 橋接、狀態和策略(在某種程度上包括適配器)模式的介面非常相似。實際上,它們都基於組合模式——即將工作委派給其他對象,各自解決了不同的問題。
C# 解決方釘與圓孔問題
Adapter.cs 適配器模式
namespace 適配器模式;
public class RoundHole
{
public double Radius { private set; get; }
public RoundHole(double radius)
{
Radius = radius;
}
public bool fit(Round r)
{
return r.getRadius() <= Radius;
}
}
public class Round
{
private double radius;
protected Round()
{
}
public Round(double radius)
{
this.radius = radius;
}
public virtual double getRadius()
{
return radius;
}
}
public class Square
{
public double Side { private set; get; }
public Square(double side)
{
Side = side;
}
public double 對角線一半長()
{
return Side / 2 * Math.Sqrt(2);
}
}
public class RoundAdapter : Round
{
private Square s; // 也可以是介面
public RoundAdapter(Square s)
{
this.s = s;
}
public void setSquare(Square s)
{
this.s = s;
}
public override double getRadius()
{
return s.對角線一半長();
}
// ... 數據轉換,重寫介面方法等
}
Program.cs
using 適配器模式;
RoundHole hole = new(5);
Round r1 = new(1);
Round r2 = new(5);
Round r3 = new(10);
Console.WriteLine(hole.fit(r1));
Console.WriteLine(hole.fit(r2));
Console.WriteLine(hole.fit(r3));
Square s1 = new(5);
RoundAdapter adapter = new(s1);
Console.WriteLine(hole.fit(adapter));
adapter.setSquare(new Square(20));
Console.WriteLine(hole.fit(adapter));
Output
True
True
False
True
False
參考資料
- 《Go語言核心編程》李文塔
- 《Go語言高級編程》柴樹彬、曹春輝
- 《大話設計模式》程傑
- 《深入設計模式》亞歷山大·什韋茨
- 菜鳥教程