1. 簡介
介面卡模式(Adapter)是將一個類(物件)的介面(方法或屬性)轉化成客戶希望的另外一個介面(方法或屬性),介面卡模式使得原本由於介面不相容而不能一起工作的那些類(物件)可以一些工作。
2. 實現
我們來舉一個例子,鴨子(Dock)有飛(fly)和嘎嘎叫(quack)的行為,而火雞雖然也有飛(fly)的行為,但是其叫聲是咯咯的(gobble)。如果你非要火雞也要實現嘎嘎叫(quack)這個動作,那我們可以複用鴨子的quack方法,但是具體的叫還應該是咯咯的,此時,我們就可以建立一個火雞的介面卡,以便讓火雞也支援quack方法,其內部還是要呼叫gobble。
OK,我們開始一步一步實現,首先要先定義鴨子和火雞的抽象行為,也就是各自的方法函式:
// 鴨子抽象類
var Duck = function(){
};
Duck.prototype.fly = function(){
throw new Error("該方法必須被重寫!");
};
Duck.prototype.quack = function(){
throw new Error("該方法必須被重寫!");
}
// 火雞抽象類
var Turkey = function(){
};
Turkey.prototype.fly = function(){
throw new Error(" 該方法必須被重寫 !");
};
Turkey.prototype.gobble = function(){
throw new Error(" 該方法必須被重寫 !");
};
然後再定義具體的鴨子和火雞的建構函式,分別為:
//鴨子
var MallardDuck = function () {
Duck.apply(this);
};
MallardDuck.prototype = new Duck(); //原型是Duck
MallardDuck.prototype.fly = function () {
console.log("可以飛翔很長的距離!");
};
MallardDuck.prototype.quack = function () {
console.log("嘎嘎!嘎嘎!");
};
//火雞
var WildTurkey = function () {
Turkey.apply(this);
};
WildTurkey.prototype = new Turkey(); //原型是Turkey
WildTurkey.prototype.fly = function () {
console.log("飛翔的距離貌似有點短!");
};
WildTurkey.prototype.gobble = function () {
console.log("咯咯!咯咯!");
};
為了讓火雞也支援quack方法,我們建立了一個新的火雞介面卡TurkeyAdapter:
var TurkeyAdapter = function(oTurkey){
Duck.apply(this);
this.oTurkey = oTurkey;
};
TurkeyAdapter.prototype = new Duck();
TurkeyAdapter.prototype.quack = function(){
this.oTurkey.gobble();
};
TurkeyAdapter.prototype.fly = function(){
var nFly = 0;
var nLenFly = 5;
for(; nFly < nLenFly;){
this.oTurkey.fly();
nFly = nFly + 1;
}
};
該建構函式接受一個火雞的例項物件,然後使用Duck進行apply,其介面卡原型是Duck,然後要重新修改其原型的quack方法,以便內部呼叫oTurkey.gobble()方法。其fly方法也做了一些改變,讓火雞連續飛5次(內部也是呼叫自身的oTurkey.fly()方法)。
呼叫方法,就很明瞭了,測試一下便可以知道結果了:
var oMallardDuck = new MallardDuck();
var oWildTurkey = new WildTurkey();
var oTurkeyAdapter = new TurkeyAdapter(oWildTurkey);
//原有的鴨子行為
oMallardDuck.fly();
oMallardDuck.quack();
//原有的火雞行為
oWildTurkey.fly();
oWildTurkey.gobble();
//介面卡火雞的行為(火雞呼叫鴨子的方法名稱)
oTurkeyAdapter.fly();
oTurkeyAdapter.quack();
3. 其它應用
介面卡模式也經常用於日常的資料處理上,比如把一個有序的陣列轉化成我們需要的物件格式:
const arr = ['Javascript', 'book', '前端程式語言', '8月1日']
function arr2objAdapter(arr) { // 轉化成我們需要的資料結構
return {
name: arr[0],
type: arr[1],
title: arr[2],
time: arr[3]
}
}
const adapterData = arr2objAdapter(arr)
在前後端的資料傳遞的時候會經常使用到介面卡模式,如果後端的資料經常變化,比如在某些網站拉取的資料,後端有時無法控制資料的格式,所以在使用資料前最好對資料進行適配成我們可用的資料格式再使用。
4. 總結
那合適使用介面卡模式好呢?如果有以下情況出現時,建議使用:
- 使用一個已經存在的物件,但其方法或屬性介面不符合你的要求;
- 你想建立一個可複用的物件,該物件可以與其它不相關的物件或不可見物件(即介面方法或屬性不相容的物件)協同工作;
- 想使用已經存在的物件,但是不能對每一個都進行原型繼承以匹配它的介面。物件介面卡可以適配它的父物件介面方法或屬性。
另外,介面卡模式和其它幾個模式可能容易讓人迷惑,這裡說一下大概的區別:
- 介面卡和橋接模式雖然類似,但橋接的出發點不同,橋接的目的是將介面部分和實現部分分離,從而對他們可以更為容易也相對獨立的加以改變。而介面卡則意味著改變一個已有物件的介面。
- 裝飾者模式增強了其它物件的功能而同時又不改變它的介面,因此它對應程式的透明性比介面卡要好,其結果是裝飾者支援遞迴組合,而純粹使用介面卡則是不可能的。
- 代理模式在不改變它的介面的條件下,為另外一個物件定義了一個代理。
本文是系列文章,可以相互參考印證,共同進步~
網上的帖子大多深淺不一,甚至有些前後矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出~
參考:
《Javascript 設計模式》 - 張榮銘
設計模式之介面卡模式