零。鴨子類型(API 契約)
開始之前先看點有意思的東西,鴨子類型 簡單的說就是「行為決定類型」。
不同於 Java 等有強制編譯時類型檢查的語言,JavaScript 是動態類型的,支持鴨子類型。例如:
function getDinner(chief) {
chief.cook('DINNER');
}
var littleBoy = {
cook: function() {
// ...
}
}
var dog = {
wang: {
// ...
}
}
var dinner1 = getDinner(littleBoy);
// var dinner2 = getDinner(dog); // Uncaught TypeError: chief.cook is not a function
我們定義了一個 getDinner 方法,參數要求是 chief,相當於在語義上約束了類型 Chief,該類型的定義是只要有 cook 方法的都算。從上面的測試結果可以看出:類型檢查是發生在運行時(Runtime)的,如果類型不符就拋出 Error。嗯,「行為決定類型」沒錯吧?
一。工廠模式
function LittleDog() {
// ...
}
function LovelyDog() {
// ...
}
function BeautifulDog() {
// ...
}
function DogFactory() {}
DogFactory.prototype.type = LittleDog; // 預設創建 LittleDog
DogFactory.prototype.getDog = function(spec) {
spec = spec || {};
if (spec.type === 'LovelyDog') {
this.type = LovelyDog;
}
else if (spec.type === 'BeautifulDog') {
this.type = BeautifulDog;
}
return new this.type(spec);
}
// test
var dogFactory = new DogFactory();
var dog1 = dogFactory.getDog(); // 創建預設的 littledog
var dog2 = dogFactory.getDog({
type: 'LovelyDog',
attr: 'val' // 其他初始化數據,由 spec 傳遞給具體構造器
});
上面的實現就是 JavaScript 版的工廠模式,把工廠的所有屬性都放在了原型對象上,在 getDog 內部覆蓋原型對象上的 type,有點單例模式的味道。
二。工廠模式的優缺點
###1. 優點
- 降低了創建對象過程的複雜性
工廠屏蔽了對象與當前環境的聯繫,調用工廠創建對象是不需要考慮當前環境
- 易於管理相似的小型對象或者組件
對象/組件之間只有些微差異時,使用工廠模式直接生產對象是比繼承更好的選擇
- 便於生產「鴨子類型」的對象
比如定義了一個鴨子類型 Chief,可以用工廠方法來生產該類型的對象(在工廠方法裡給對象添上 cook 方法即可),而不需要為了定義類型而定義類型
###2. 缺點
- 存在額外的開銷
工廠模式的本質是對一組相關構造函數的二次封裝,因此會帶來額外的開銷(多了一層)
- 不利於單元測試
對象創建的過程被藏在了工廠後面,如果對象創建過程非常複雜的話,可能會給單元測試帶來問題
三。抽象工廠模式
用 JavaScript 也可以實現抽象工廠模式,而且實現起來要比 Java 簡單一些,示例代碼如下:
var factory = (function() {
// 存儲車輛類型
var types = {};
return {
getVechicle: function(type, customizations) {
var Vechicle = types[type];
return (Vechicle) ? new Vechicle(customizations) : null;
},
registerVechicle: function(type, Vechicle) {
var proto = Vechicle.prototype;
// 只註冊實現車輛契約的類
if (proto.drive && proto.breakDown) {
types[type] = Vechicle;
}
}
}
})();
抽象工廠提供 registerXXX 接口統一管理構造方法,並在內部用「API 契約」約束類型。註冊過構造方法後調用 getVechicle 創建對象即可,例如:
// 構造方法(簡單起見,直接返回參數對象)
function Car(arg) {
return arg;
}
Car.prototype.drive = function() {};
Car.prototype.breakDown = function() {};
function Truck(arg) {
return arg;
}
Truck.prototype = Car.prototype;
// 用法
factory.registerVechicle('car', Car);
factory.registerVechicle('truck', Truck);
// 基於抽象車輛類型實例化一個新 car 對象
var car = factory.getVechicle('car', {
color: 'lime green',
state: 'like new'
});
// 同理,實例化一個新 truck 對象
var truck = factory.getVechicle('truck', {
wheelSize: 'medium',
color: 'neon yellow'
});
console.log(car);
console.log(truck);
對比工廠模式的第一個例子,這裡其實只是把構造函數和工廠分離開了(把 if(type === 'xxx') 從 factory.create 裡拿出來了),可以靈活地註冊構造函數
和經典的抽象工廠模式的意義有很大差別,最多只能算是對工廠模式的一種增強(優化),但這都沒關係,畢竟不是為了追求使用設計模式而使用,只要能達到適度解耦的目的就好
關於抽象工廠模式的更多信息,請查看參考資料部分引用的 2 篇博文
參考資料
-
《JavaScript 設計模式》
暫無評論,快來發表你的看法吧