Javascript 設計模式之工廠模式

爲何使用工廠模式

解答問題前,瞭解什麼是工廠模式我以爲更重要些。 工廠模式其實也稱建立模式,是用於建立對象的一種方式。能夠說就是用來代替 new 實例化對象,決定了實例化哪個類,從而解決解耦問題。javascript

舉個例子:html

  • 編程中,在一個 A 類中經過 new 的方式實例化了類 B,那麼 A 類和 B 類之間就存在關聯(耦合);
  • 後期由於須要修改了 B 類的代碼和使用方式,好比構造函數中傳入參數,那麼 A 類也要跟着修改,一個類的依賴可能影響不大,但如有多個類依賴了 B 類,那麼這個工做量將會至關的大,容易出現修改錯誤,也會產生不少的重複代碼,這無疑是件很是痛苦的事;
  • 這種狀況下,就須要將建立實例的工做從調用方(A類)中分離,與調用方解耦,也就是使用工廠方法建立實例的工做封裝起來(減小代碼重複),由工廠管理對象的建立邏輯,調用方不須要知道具體的建立過程,只管使用,而下降調用者由於建立邏輯致使的錯誤

擬物化解讀

一個工廠接到一筆訂單(傳參),而後根據這個訂單類型(參數)來安排產品線(實例化哪一個類),固然客戶能夠要求一些產品的工藝屬性(抽象工廠)。這其中廠長(工廠模式)只負責調度,即安排產品零件流水線。你應該知道的是,工廠有個特色就是產出體量大、類似度高的產品。若是你要作單必定製化的產品,那這筆訂單給工廠就不適用了。java

其做用(利)

  • 解耦,經過使用工程方法而不是 new 關鍵字;
  • 將全部實例化的代碼集中在一個位置減小代碼重複,下降出錯;

具體實現

  • 分步建立一個複雜的對象,解耦封裝過程和具體建立組件(分解爲零件流水線);
  • 無需關心組件如何組裝(廠長在調度);
  • 不暴露建立對象的具體邏輯,將邏輯封裝在一個函數中(客戶只須要告訴工廠作什麼和提一些要求);

適用場景

  • 處理大量具備相同屬性的小對象;
  • 對象的構建十分複雜,須要依賴具體環境建立不一樣實例;

分類(抽象程度)

不暴露建立對象的具體邏輯,而是將將邏輯封裝在一個函數中,那麼這個函數就能夠被視爲一個工廠。node

抽象程度

簡單工廠模式

也能夠叫靜態工廠模式,用一個工廠對象建立同一類對象類的實例。現實生活中,用戶在平臺仍是分等級的,角色不一樣,權限也不一樣。jquery

1.ES5 實現git

// 0.0.2/es5.sample.factory.js
function Role(options){
    this.role = options.role;
    this.permissions = options.permissions;
}
Role.prototype.show = function (){
    var str = '是一個' + this.role + ', 權限:' + this.permissions.join(', ');
    console.log(str)
}

function sampleFactory(role){
    switch(role) {
        case 'admin':
            return new Role({ 
                role: '管理員', 
                permissions: ['設置', '刪除', '新增', '建立', '開發', '推送', '提問', '評論']
            });
            break;
        case 'developer':
            return new Role({ 
                role: '開發者', 
                permissions: ['開發', '推送', '提問', '評論']
            });
            break;
        default:
            throw new Error('參數只能爲 admin 或 developer');
    }
}

// 實例
const xm = sampleFactory('admin');
xm.show();

const xh = sampleFactory('developer');
xh.show();

const xl = sampleFactory('guest');
xl.show();
複製代碼

2.ES6 實現github

// 0.0.2/sample.factory.js
class SampleFactory {
    constructor(opt) {
        this.role = opt.role;
        this.permissions = opt.permissions;
    }

    // 靜態方法
    static create(role) {
        switch (role) {
            case 'admin':
                return new SampleFactory({
                    role: '管理員',
                    permissions: ['設置', '刪除', '新增', '建立', '開發', '推送', '提問', '評論']
                });
                break;
            case 'developer':
                return new SampleFactory({
                    role: '開發者',
                    permissions: ['開發', '推送', '提問', '評論']
                });
                break;
            default:
                throw new Error('參數只能爲 admin 或 developer');
        }
    }

    show() {
        const str = `是一個${this.role}, 權限:${this.permissions.join(', ')}`;
        console.log(str);
    }

}

// 實例
const xm = SampleFactory.create('admin');
xm.show();

const xh = SampleFactory.create('developer');
xh.show();

const xl = SampleFactory.create('guest');
xl.show();
複製代碼

編程

// 0.0.2/sample.factory1.js
class Role {
    constructor(options) {
        this.role = options.role;
        this.permissions = options.permissions;
    }
    show() {
        const str = `是一個${this.role}, 權限:${this.permissions.join(', ')}`;
        console.log(str);
    }
}
class SampleFactory {
    constructor(role) {
        this.role = role;
    }

    // 靜態方法
    static create(role) {
        switch (role) {
            case 'admin':
                return new Role({
                    role: '管理員',
                    permissions: ['設置', '刪除', '新增', '建立', '開發', '推送', '提問', '評論']
                });
                break;
            case 'developer':
                return new Role({
                    role: '開發者',
                    permissions: ['開發', '推送', '提問', '評論']
                });
                break;
            default:
                throw new Error('參數只能爲 admin 或 developer');
        }
    }
}

// 實例
const xm = SampleFactory.create('admin');
xm.show();

const xh = SampleFactory.create('developer');
xh.show();

const xl = SampleFactory.create('guest');
xl.show();
複製代碼

設計模式

// 0.0.2/sample.factory2.js
class Role {
    constructor(options) {
        this.role = options.role;
        this.permissions = options.permissions;
    }
    show() {
        const str = `是一個${this.role}, 權限:${this.permissions.join(', ')}`;
        console.log(str);
    }
}

class SampleFactory {
    constructor(role) {
        if(typeof this[role] !== 'function') {
            throw new Error('參數只能爲 admin 或 developer');
        }
        return this[role]();
    }

    admin() {
        return new Role({
            role: '管理員',
            permissions: ['設置', '刪除', '新增', '建立', '開發', '推送', '提問', '評論']
        });
    }
    developer() {
        return new Role({
            role: '開發者',
            permissions: ['開發', '推送', '提問', '評論']
        });
    }
}


// 實例
const xm = new SampleFactory('admin');
xm.show();

const xh = new SampleFactory('developer');
xh.show();

const xl = new SampleFactory('guest');
xl.show();
複製代碼

上例中,sampleFactory 就是一個簡單工廠,2個實例對應不一樣的權限,調用工廠函數時,只需傳遞 admindeveloper 就可獲取對應的實例對象。緩存

1.簡單工廠函數適用場景

  • 正確傳參,就能夠獲取所須要的對象,無需知道內部實現細節;
  • 內部邏輯(工廠函數)經過傳入參數判斷實例化仍是使用哪些類;
  • 建立對象數量少(穩定),對象的建立邏輯不復雜;

2.簡單工廠函數不適用場景

  • 當須要添加新的類時,就須要修改工廠方法,這違背了開放封閉原則(OCP, 對擴展開放、對源碼修改封閉)。正所謂成也蕭何敗也蕭何。函數 create 內包含了全部建立對象(構造函數)的判斷邏輯代碼,若是要增長新的構造函數還須要修改函數 create(判斷邏輯代碼),當可選參數 role 變得更多時,那函數 create 的判斷邏輯代碼就變得臃腫起來,難以維護。
  • 不適用建立多類對象;

工廠方法模式

將實際建立對象工做推遲到子類當中,核心類就成了抽象類。這樣添加新的類時就無需修改工廠方法,只須要將子類註冊進工廠方法的原型對象中便可。

1.安全模式類,能夠屏蔽使用類的錯誤形成的錯誤

// 0.0.2/secure.function.factory.js
function Factory(){
    if(!(this instanceof Factory)) {
        return new Factory();
    }
}
Factory.prototype.show = function(){
    console.log('factory show');
}
var f = new Factory();
f.show();
複製代碼

2.ES5 實現,ES5 沒有像傳統建立類的方式那樣建立抽象類,因此工廠方法模式只需參考其核心思想便可。可將工廠方法看作一個實例化對象工廠類(採用安全模式類),將建立對象的基類放在工廠方法類的原型中便可。當須要添加新類時,只需掛載在 FunctionFactory.prototype 上,無需修改工廠方法,也實現了 OCP 原則。

// 0.0.2/es5.function.factory.js
function FunctionFactory(role) {
    if(!(['admin', 'developer'].indexOf(role) > -1)){
        throw new Error('參數只能爲 admin 或 developer');
    }
    
    // 安全的工廠方法
    if (this instanceof FunctionFactory) {
        return this[role]();
    }
    return new FunctionFactory(role);
}
FunctionFactory.prototype.show = function () {
    var str = '是一個' + this.role + ', 權限:' + this.permissions.join(', ');
    console.log(str)
}
FunctionFactory.prototype.admin = function (permissions) {
    this.role = '管理員';
    this.permissions = ['設置', '刪除', '新增', '建立', '開發', '推送', '提問', '評論'];
}
FunctionFactory.prototype.developer = function (permissions) {
    this.role = '開發者';
    this.permissions = ['開發', '推送', '提問', '評論'];
}

var xm = FunctionFactory('admin');
xm.show();

var xh = new FunctionFactory('developer');
xh.show();

var xl = new FunctionFactory('guest');
xl.show();
複製代碼

3.ES6 實現,因爲 ES6 中尚未 abstract,就用 new.target 來模擬出抽象類(new.target 指向被 new 執行的構造函數),判斷 new.target 是否指向了抽象類,若是是就報錯。

// 0.0.2/function.factory.js
class FunctionFactoryBase { // 抽象類
    constructor(role) {
        if (new.target === FunctionFactoryBase) {
            throw new Error('抽象類不能實例');
        }
        this.role = role;
    }
}

class FunctionFactory extends FunctionFactoryBase { // 子類
    constructor(role) {
        super(role);
    }

    static create(role) {
        switch (role) {
            case 'admin':
                return new FunctionFactory({
                    role: '管理員',
                    permissions: ['設置', '刪除', '新增', '建立', '開發', '推送', '提問', '評論']
                });
                break;
            case 'developer':
                return new FunctionFactory({
                    role: '開發者',
                    permissions: ['開發', '推送', '提問', '評論']
                });
                break;
            default:
                throw new Error('參數只能爲 admin 或 developer');
        }
    }

    show() {
        const { role, permissions } = this.role;
        const str = `是一個${role}, 權限:${permissions.join(', ')}`;
        console.log(str)
    }
}

// let xl = new FunctionFactoryBase(); // 此行會報錯,註釋後方可正常執行後面

let xm = FunctionFactory.create('admin');
xm.show()

let xh = FunctionFactory.create('developer');
xh.show()

let xl = FunctionFactory.create('guest');
xl.show()
複製代碼

抽象工廠模式

抽象工廠只留對外的口子,不作事,留給外界覆蓋(子類重寫接口方法以便建立的時候指定本身的對象類型)。主要用於對產品類簇的建立,不直接生成實例(簡單工廠模式和工廠方法模式都是生成實例)。

  • 抽象類是一種聲明但不能使用的類,子類必須先實現其方法才能調用;
  • 能夠在抽象類中定義一套規範,供子類去繼承實現;
// 0.0.2/abstract.factory2.js
// 抽象工廠
function AbstractFactory(subType, superType) {
    if (typeof AbstractFactory[superType] === 'function') {
        //緩存類
        function F() { }
        //繼承父類屬性和方法
        F.prototype = new AbstractFactory[superType]();
        //將子類 constructor 指向子類(本身)
        subType.prototype.constructor = subType;
        //子類原型繼承緩存類(父類)
        subType.prototype = new F();
    } else {
        //不存在該抽象類拋出錯誤
        throw new Error('抽象類不存在')
    }
}

// 抽象類
AbstractFactory.Phone = function () {
    this.type = 'Phone';
}
AbstractFactory.Phone.prototype = {
    showType: function () {
        return new Error('Phone 抽象方法 showType 不能調用');
    },
    showPrice: function () {
        return new Error('Phone 抽象方法 showPrice 不能調用');
    },
    showColor: function () {
        return new Error('Phone 抽象方法 showColor 不能調用');
    }
}

AbstractFactory.Pad = function () {
    this.type = 'Pad';
}
AbstractFactory.Pad.prototype = {
    showType: function () {
        return new Error('Pad 抽象方法 showType 不能調用');
    },
    showPrice: function () {
        return new Error('Pad 抽象方法 showPrice 不能調用');
    },
    showColor: function () {
        return new Error('Pad 抽象方法 showColor 不能調用');
    }
}

// 抽象工廠實現對抽象類的繼承
function Iphone(type, price, color) {
    this.type = type;
    this.price = price;
    this.color = color;
}

//抽象工廠實現對 Phone 抽象類的繼承
AbstractFactory(Iphone, 'Phone');
Iphone.prototype.showType = function () {
    return this.type;
}
Iphone.prototype.showPrice = function () {
    return this.price;
}
Iphone.prototype.showColor = function () {
    return this.color;
}

function Ipad(type, price, color) {
    this.type = type;
    this.price = price;
    this.color = color;
}
AbstractFactory(Ipad, 'Pad');
Ipad.prototype.showType = function () {
    return this.type;
}
Ipad.prototype.showPrice = function () {
    return this.price;
}
Ipad.prototype.showColor = function () {
    return this.color;
}

// 實例
var iphone5s = new Iphone('iphone 5s', 3000, '白色');
console.log('今天剛買了' + iphone5s.showType() + ',價格是' + iphone5s.showPrice() + ',' + iphone5s.showColor())

var iphone8s = new Iphone('iphone 8s', 8000, '白色');
console.log('今天剛買了' + iphone8s.showType() + ',價格是' + iphone8s.showPrice() + ',' + iphone8s.showColor())

var ipad = new Ipad('ipad air', 2000, '騷紅色');
console.log('今天剛買了' + ipad.showType() + ',價格是' + ipad.showPrice() + ',' + ipad.showColor())
複製代碼

實戰示例

1.jQuery源碼-工廠模式

// 0.0.2/jquery.factory.js
// 工廠模式
class jQuery {
  constructor(selector) {
    let slice = Array.prototype.slice;
    let dom = slice.call(document.querySelectorAll(selector));
    let len = dom ? dom.length : 0;
    for (let i = 0; i < len; i++) {
      this[i] = dom[i];
    }
    this.length = len
    this.selector = selector || ''
  }
  addClass(name) {
    console.log(name)
  }
  html(data) {

  }
  // 省略多個 API
}

// 工廠模式
window.$ = function(selector) {
  return new jQuery(selector);
}

// 實例
const $li = $('li') 
$li.addClass('item');
複製代碼

2.React.createElement 實現

// jsx
var profile = (
  <div> <img src='https://raw.githubusercontent.com/ruizhengyun/images/master/cover/ruizhengyun.cn_.png' className="profile" /> <h3>{[user.firstName, user.lastName].join(' ')}</h3> </div> ); // 實現 var profile = React.createElement('div', null, React.createElement('img', { src: 'https://raw.githubusercontent.com/ruizhengyun/images/master/cover/ruizhengyun.cn_.png', className: 'profile' }), React.createElement('h3', null, [user.firstName, user.lastName].join(' ')) ); // 源碼 class Vnode(tag, attrs, children) { // ... } React.createElement = function(tag, attrs, children) { return new Vnode(tag, attrs, children); } 複製代碼

設計原則驗證

  • 構造函數與建立者分離
  • 符合開放封閉原則

閱讀源碼(lib)意義

  • 學習如何實現功能(招式)
  • 學習設計思路(心法)
  • 刻意模擬學習
  • 寫出愉悅的代碼

本次代碼 Github

你能夠

目錄:Javascript 設計模式小書

上一篇:Javascript 設計模式之設計原則

下一篇:Javascript 設計模式之單例模式

相關文章
相關標籤/搜索