意圖
把一個類的接口變換成客戶端所期待的另一種接口,從而使原本接口不匹配而無法在一起工作的兩個類能夠在一起工作。
場景
假設網絡游戲的客戶端程序分兩部分。一部分是和服務端通訊的大廳部分,大廳部分提供的功能有道具購買、讀取房間列表、創建房間以及啟動游戲程 序。另一部分就是游戲程序了,游戲程序和大廳程序雖然屬於一個客戶端,但是由不同的公司在進行開發。游戲大廳通過實現約定的接口和游戲程序進行通訊。
一開始的設計就是,大廳程序是基於接口方式調用游戲程序啟動游戲場景方法的。在大廳程序開發接近完成的時候,公司決定和另外一家游戲公司合作, 因此希望把大廳程序能適用另一個游戲。而這個新游戲的遵循的是另一套接口。是不是可以避免修改原先調用方法來啟動場景呢?或許你會說,既然只有一個方法修 改,那麼修改一下也無妨,我們假設大廳程序和游戲程序之間有100個接口,其中的大部分都有修改呢?因為游戲程序接口的修改,大廳程序可能要修改不止 100個地方。這樣接口的意義何在呢?
此時可以考慮使用Adapter模式來適配這種接口的不匹配情況。
以下為引用的內容:
using System; using System.Collections.Generic; using System.Text; namespace AdapterExample { class Program { static void Main(string[] args) { Lobby lobby = new Lobby(); lobby.CreateRoom("HalfPaper"); lobby.StartGame(); } } interface IGame { void StartScene(string sceneName); void EnterPlayer(string playerName); } class Lobby { private string sceneName; public void CreateRoom(string sceneName) { this.sceneName = sceneName; } public void StartGame() { IGame game = new GameAdapter(); game.StartScene(sceneName); game.EnterPlayer("yzhu"); } } class Game { public void LoadScene(string sceneName, string token) { if (token == "Abcd1234") Console.WriteLine("Loading " + sceneName + "..."); else Console.WriteLine("Invalid token!"); } public void EnterPlayer(int playerID) { Console.WriteLine("player:" + playerID + " entered"); } } class GameAdapter : IGame { private Game game = new Game(); public void StartScene(string sceneName) { game.LoadScene(sceneName, "Abcd1234"); } public void EnterPlayer(string playerName) { game.EnterPlayer(GetPlayerIDByPlayerName(playerName)); } private int GetPlayerIDByPlayerName(string playerName) { return 12345; } } }
可以看到,原先的接口中,啟動游戲場景只需要一個參數,就是游戲場景名,而進入新的玩家需要提供玩家ID(新游戲都使用玩家ID而不使用玩家賬戶名)。
IGame接口就是適配器模式中的目標角色,這是客戶所期待的接口。也是針對老的游戲程序所遵循的接口。
Lobby類相當於調用方或者客戶,它原先的代碼可能是如下的:
以下為引用的內容:
Game game = new Game();
但是由於接口的改變,現在不能直接實例化游戲類,只能實例化適配器類型。雖然還是需要改動,但是這個改動是很小的,而且完全可以通過用動態加載程序集來消除這種改動。
GameAdapter類是適配器角色,它是適配器模式的核心,用於把源接口轉變為目標接口。在這裡,我們看到,它實現目標接口。
Game類型是源角色,或者說是需要適配的對象。或許它也遵循了另外一套接口,不過我們不是很關心這個,因此代碼中也沒有體現。
使用了適配器模式後,客戶端代碼沒有做什麼修改。客戶端代碼老老實實的依賴接口,它並沒有錯,如果因此依賴對象的修改而需要大幅度修改就很無辜 了,我們在適配器中把本來沒有關聯的兩個接口適配在了一起。我們可以看到,適配器做的不僅僅是換一換方法名,如果源角色和目標角色的差異非常大,那麼適配 器需要做很多工作。
何時采用
從代碼角度來說, 如果你希望分離復雜類型構建規則和類型內部組成,或者希望把相同的構建過程用於構建不同類型的時候可以考慮使用建造者模式。
從應用角度來說, 如果你希望解耦產品的創建過程和產品的具體配件,或者你希望為所有產品的創建復用一套穩定並且復雜的邏輯的時候可以考慮使用建造者模式。
實現要點
適配器模式是否能成功運用的關鍵在於代碼本身是否是基於接口編程的,如果不是的話,那麼適配器無能為力。
適配器模式的實現很簡單,基本的思想就是適配器一定是遵循目標接口的。
適配器模式的變化比較多,可以通過繼承和組合方式進行適配,適配器可以是一組適配器產品,適配器也可以是抽象類型。
適配器模式和Facade的區別是,前者是遵循接口的,後者可以是不遵循接口的,比較靈活。
適配器模式和Proxy的區別是,前者是為對象提供不同的接口,或者為對象提供相同接口,並且前者有一點後補的味道,後者是在設計時就會運用的。
注意事項
在對兩個無關類進行適配的時候考慮一下適配的代價,一個非常龐大的適配器可能會對系統性能有影響。