轉接器模式(Adapter)
轉接器模式(Adapter),將一個類別的介面轉換成客戶希望的另一個介面。Adapter模式使得原本由於介面不相容而不能一起工作的那些類別可以一起工作。
主要解決什麼?
簡單來說,就是需要的東西就在前面,但卻不能使用,而短時間又無法改造它,於是我們就想辦法轉接它。
在軟體開發中,系統的資料和行為都正確,但介面不符時,我們應該考慮用轉接器模式,目的是使控制範圍之外的一個原有物件與某個介面匹配。
主要應用
轉接器模式主要應用於希望複用一些既有的類別,但是介面又與複用環境要求不一致的情況,比如在需要對早期程式碼複用一些功能等應用上很有實際價值。
何時用?
在想使用一個已經存在的類別,但如果它的介面,也就是它的方法和你的要求不相同時,就應該考慮用「轉接器模式(Adapter)」。
兩個類別所做的事情相同或相似,但是具有不同的介面時要使用它。而且由於類別都共用一個介面,使得客戶程式碼可以統一調用同一個介面就行了。這樣可以更簡單、更直接、更緊湊。
在公司內部,類別和方法的命名該有規範,最好前期就設計好,然後如果真的介面不相同時,首先不應該考慮用轉接器,而是應該考慮透過重構統一介面。
就是在雙方都不太容易修改時,再使用轉接器模式轉接,而不是一有不同時就使用它。
那有沒有設計之初就需要考慮使用轉接器模式(Adapter)?
當然有,比如公司設計一系統時考慮使用第三方開發元件,
轉接器模式的.NET應用
比如,在.NET 中有一個類別庫已經實現的非常重要的轉接器DataAdapter。
轉接器模式亂用不如不用。如果無視它的應用場合而盲目使用,其實是本末倒置了。
如果能事先預防介面不同的問題,不匹配問題就不會發生;在有小的介面不統一問題發生時,及時重構,問題不至於擴大;只有碰到無法改變原有設計和程式碼的情況,才考慮轉接。事後控制不如事中控制,事中控制不如事前控制。
結構
Target這是客戶所期待的介面。目標可以是具體的或抽象的類別,也可以是介面Adapter透過在內部包裝一個Adaptee物件,把原始介面轉換成目標介面Adaptee需要適配的類別
1
2
3
4
5
6
7
8
9
10
11
12
Client
- target
Target 這是客戶所期待的介面。目標可以是具體的或抽象的類別,也可以是介面
+ Request()
Adapter 透過在內部包裝一個Adaptee物件,把原始介面轉換成目標介面
- adaptee
+ Request()
Adaptee 需要適配的類別
+ SpecificRequest()
程式碼
Target
Target 這是客戶所期待的介面。目標可以是具體的或抽象的類別,也可以是介面。
1
2
3
4
5
6
//Target 這是客戶所期待的介面。目標可以是具體的或抽象的類別,也可以是介面
class Target {
public virtual void Request() {
Console.WriteLine("普通請求!");
}
}
Adapter
Adapter 透過在內部包裝一個Adaptee物件,把原始介面轉換成目標介面。
1
2
3
4
5
6
7
8
9
//Adapter 透過在內部包裝一個Adaptee物件,把原始介面轉換成目標介面
class Adapter: Target {
//建立一個私有的Adaptee物件
private Adaptee adaptee = new Adaptee();
//把表面上調用Request()方法變成實際調用SpecificRequest()
public override void Request() {
adaptee.SpecificRequest();
}
}
Adaptee
Adaptee 需要適配的類別。
1
2
3
4
5
6
//Adaptee 需要適配的類別
class Adaptee {
public void SpecificRequest() {
Console.WriteLine("特殊請求!");
}
}
用戶端
對用戶端來說,用的就是Target的Request()。
1
2
3
//對用戶端來說,用的就是Target的Request()
Target target = new Target();
target.Request();
籃球翻譯轉接器
因為有了翻譯者,教練和球員不會英文,也能團隊溝通合作。
球員類別
1
2
3
4
5
6
7
8
9
10
//球員
abstract class Player {
protected string name;
public Player(string name) {
this.name = name;
}
public abstract void Attack(); //進攻
public abstract void Defense(); //防守
}
前鋒、中鋒、後衛類別
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//前鋒
class Forwards: Player {
public Forwards(string name): base(name) { }
//進攻
public override void Attack() {
Console.WriteLine($"前鋒{name} 進攻");
}
//防守
public override void Defense() {
Console.WriteLine($"前鋒{name} 防守");
}
}
//中鋒
class Center: Player {
//與前鋒類似,略
}
//後衛
class Guards: Player {
//與前鋒類似,略
}
用戶端程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Player forwards = new Forwards("AA");
forwards.Attack();
Player center = new Center("BB");
center.Attack();
Player guards = new Guards("CC");
guards.Attack();
guards.Defense();
/*
前鋒AA 進攻
中鋒BB 進攻
後衛CC 進攻
後衛CC 防守
*/
外籍中鋒
1
2
3
4
5
6
7
8
9
10
11
12
13
class ForeignCenter {
//姓名故意用屬性而不是用建構式來區別與前三個球員類別的不同
public string Name{ get; set; }
//進攻
public void 進攻() {
Console.WriteLine($"外籍中鋒{this.Name} 進攻");
}
//防守
public void 防守() {
Console.WriteLine($"外籍中鋒{this.Name} 防守");
}
}
翻譯類別
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//翻譯者
class Translator: Player {
//宣告並實體化一個內部「外籍中鋒」物件,表明翻譯者與外籍球員有關
private ForeignCenter fc = new ForeignCenter();
public Translator(string name): base(name) {
fc.Name = name;
}
//進攻: 翻譯者將「Attack」翻譯為「進攻」告訴外籍球員
public override void Attack() {
fc.進攻();
}
//防守: 翻譯者將「Defense」翻譯為「防守」告訴外籍球員
public override void Defense() {
fc.防守();
}
}
用戶端改寫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void Main()
{
Player forwards = new Forwards("AA");
forwards.Attack();
Player center = new Center("BB");
center.Attack();
//翻譯者告訴CC,教練要求你既要「進攻」又要「防守」
Player guards = new Translator("CC");
guards.Attack();
guards.Defense();
}
/*
前鋒AA 進攻
中鋒BB 進攻
外籍中鋒CC 進攻
外籍中鋒CC 防守
*/