接口的改變,是一個需要程序員們必須(雖然很不情愿)接受和處理的普遍問題。程序提供者們修改他們的代碼;系統庫被修正;各種程序語言以及相關庫的發展和進化。我孩子的無數玩具中有一個簡要地描述了這個兩難局面:你無法合理安排一個不得其所的人。
問題
你如何避免因外部庫的API改變而帶來的不便?假如你寫了一個庫,你能否提供一種方法允許你軟件的現有用戶進行完美地升級,即使你已經改變了你的API?為了更好地適宜于你的需要,你應該如何改變一個對象的接口?
解決方案
適配器(Adapter)模式為對象提供了一種完全不同的接口。你可以運用適配器(Adapter)來實現一個不同的類的常見接口,同時避免了因升級和拆解客戶代碼所引起的糾紛。
考慮一下當(不是假設!)一個第三方庫的API改變將會發生什么。過去你只能是咬緊牙關修改所有的客戶代碼,而情況往往還不那么簡單。你可能正從事一項新的項目,它要用到新版本的庫所帶來的特性,但你已經擁有許多舊的應用程序,并且它們與以前舊版本的庫交互運行地很好。你將無法證明這些新特性的利用價值,如果這次升級意味著將要涉及到其它應用程序的客戶代碼。
注:控制體模式
適配器(Adapter)模式是控制體模式的最新范例。一個適配器(Adapter)的結構類似于代理服務器(Proxy)和修飾器(Decorator),而它們的不同之處在于,適配器(Adapter)的目的是改變封裝類的接口,代理服務器(Proxy)和修飾器(Decorator)則是保持接口不變。
樣本代碼
讓我們看看當API改變時,如何保護應用程序不受影響。
假設你費盡心思尋找合適的庫,最后終于找到了HwLib,一個(假設的)被設計用來發送信息的代碼集。
下面是HwLib類的源代碼:
| // PHP4 /** * the HwLib helps programmers everywhere write their first program * @package HelloWorld * @version 1 */ class HwLib { /** * Say “Hello” * @deprec this function is going away in the future * @return string */ function hello() { return ‘Hello ‘; } /** * target audience * @return string */ function world() { return ‘World!’; } } |
| $hw =& new HwLib; echo $hw->hello(), $hw->world(); |
HwLib有完備的說明文檔。在文檔中作者已經明確指出hello()方法會在未來的版本中不被支持(甚至淘汰)。
接下來,現在假設第二版的HwLib已經發布。一個全新的greet()方法代替了hello()。
下面是這個庫的新版本(注釋已被抽取掉):
| // version 2 class HwLib { function greet() { return ‘Greetings and Salutations ‘; } unction world() { return ‘World!’; } } |
為了適應HwLib的不同版本進行編碼,先進行一些基于第一版本HwLib接口的測試:
| class AdapterTestCase extends UnitTestCase { function TestOriginalApp() { $lib =& new HwLib; $this->assertEqual( ‘Hello World!’ ,$lib->hello().$lib->world()); } } |
你同樣可以表明,對這個庫的簡單升級將造成此應用程序的失效。
| class AdapterTestCase extends UnitTestCase { function TestOriginalAppWouldFail() { $lib =& new HwLib; // now using HwLib version 2 $this->assertFalse(method_exists($lib, ‘hello’)); } } |
(這個測試以method_exists()為例證。如果你簡單地更換這個庫的第二版本并且以TestOriginalApp()的測試再次運行AdapterTestCase,PHP就會運行失敗,同時報告“致命錯誤:未定義的函數:hello()”)
針對API“升級”的解決辦法就是創建一個適配器(Adapter)。
第一步是獲得第二版本HwLib的實例的一個引用,并且把它加入到你的Adapter類中。
| class HwLibV2ToV1Adapter { var $libv2; function HwLibV2ToV1Adapter (&$libv2) { $this->libv2 =& $libv2; } } |
這個范例展示了將這個實例傳遞給構造函數的過程,你也可以運用Factory 或 Singleton ,或者其它適合你要求的創建模式,來創建一個新的實例。(通過前兩章,你應該對HwLibV2ToV1Adapter的編寫用途很熟悉了)
新聞熱點
疑難解答