跳到主要內容
黯羽輕揚每天積累一點點

工廠模式_JavaScript 設計模式 9

免費2015-07-28#JS#Design_Pattern#JavaScript工厂模式#鸭子类型#API契约#抽象工厂模式

工廠模式,顧名思義,是用來創建對象的,負責封裝複雜的對象創建過程,提供簡單的功能入口。本文詳細介紹 JavaScript 實現的工廠模式

零。鴨子類型(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. 優點

  1. 降低了創建對象過程的複雜性

工廠屏蔽了對象與當前環境的聯繫,調用工廠創建對象是不需要考慮當前環境

  1. 易於管理相似的小型對象或者組件

對象/組件之間只有些微差異時,使用工廠模式直接生產對象是比繼承更好的選擇

  1. 便於生產「鴨子類型」的對象

比如定義了一個鴨子類型 Chief,可以用工廠方法來生產該類型的對象(在工廠方法裡給對象添上 cook 方法即可),而不需要為了定義類型而定義類型

###2. 缺點

  1. 存在額外的開銷

工廠模式的本質是對一組相關構造函數的二次封裝,因此會帶來額外的開銷(多了一層)

  1. 不利於單元測試

對象創建的過程被藏在了工廠後面,如果對象創建過程非常複雜的話,可能會給單元測試帶來問題

三。抽象工廠模式

用 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 篇博文

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論