JavaScript 設計模式 : 巧用'工廠模式'和'建立者'模式

莊文達發表於2017-04-27

我為什麼把他們兩個放在一起講?我覺得這兩個設計模式有相似之處,有時候會一個設計模式不能滿足你的需求而採用另一種設計模式。基於這點考慮,而且為了大家更好地理解,我放到了一起,加深大家的印象,活學活用。

[這裡我為了能更好的體現下設計模式與JS本體語言的結合,我用了一點繼承關係.
有的同學都不知道JS能繼承,就算大家不懂繼承也希望大家能看下去,弄懂它!]
複製程式碼

本文擴充套件

掘金有個文章繼承還是蠻透徹的 JS原型鏈與繼承別再被問倒了

工廠模式

  • 建立物件跟對不同需求進行不同的例項化

在我們Team協作開發過程當中,不同於我們寫個人專案,對全域性變數的限制很大,我們要儘量少的使用全域性變數,對於一類物件在不同需求上的不同使用,甚至將一些有些類似的方法抽象化,可以用工廠模式來負責建立這些物件,呼叫者可以使用一部分資源也可以在基礎上私人訂製一套資源。

就拿昨天入群的小夥伴舉個例子:他設計一個網頁播放器有四個按鈕:

JavaScript 設計模式 :  巧用'工廠模式'和'建立者'模式

我們不討論他的實現方式,我們按照工廠模式來簡單建立一個吧!

function wangyiMusicAction(action){
    var o = new Object;
    o.vender = '網易雲音樂';
    o.playingMusic = 'see you again'
    switch (action){
        case 'last':
            o.information = {currentMusic:'Ich will',status:'200|404',message:'上一曲'}
            break
        case 'next':
            o.information = {currentMusic:'一人我程式設計累',status:'200|404',message:'下一曲'}
            break
        case 'play':
            o.information = {currentMusic:'see you again',status:'200|500',message:'播放'}
            break
        case 'mute':
            o.information = {currentMusic:'see you again',status:'200|500',message:'靜音'}
            break
    }
    return o;

}
var music = wangyiMusicAction('next')
console.log('音樂提供商 : '+music.vender);
console.log('正在播放 : '+music.playingMusic);
console.log('執行動作 : ' +music.information.message);
console.log('介面狀態 : ' +music.information.status);
console.log('執行動作後歌曲 : ' +music.information.currentMusic);

----------執行結果--------

音樂提供商 : 網易雲音樂
正在播放 : see you again
執行動作 : 下一曲
介面狀態 : 200|404
執行動作後歌曲 : 一人我程式設計累
複製程式碼

這其實使我們經常使用的,不過這是程式導向的,不太符合我們的設計模式。我們用上篇學到的模式:物件

我們可以修改一下:

var WangyiMusicAction = function(action){

    this.vender = '網易雲音樂';
    this.playingMusic = 'see you again'

}

WangyiMusicAction.prototype = {
    last : function() {
            this.information = {currentMusic:'Ich will',status:'200|404',message:'上一曲'}
        },

    next : function() {
            this.information = {currentMusic:'一人我程式設計累',status:'200|404',message:'下一曲'}
        },

    play : function() {
            this.information = {currentMusic:'see you again',status:'200|500',message:'播放'}
        },

    mute : function() {
            this.information = {currentMusic:'see you again',status:'200|500',message:'靜音'}
        }

}

var music = new WangyiMusicAction()
music.next(); //執行下一曲動作
console.log('音樂提供商 : '+music.vender);
console.log('正在播放 : '+music.playingMusic);
console.log('執行動作 : ' +music.information.message);
console.log('介面狀態 : ' +music.information.status);
console.log('執行動作後歌曲 : ' +music.information.currentMusic);
音樂提供商 : 網易雲音樂
正在播放 : see you again
執行動作 : 下一曲
介面狀態 : 200|400

----------執行結果--------

執行動作後歌曲 : 一人我程式設計累

複製程式碼

這樣就算是物件導向的了,雖然達到目的,但是上面所說的,但是這算是Music的網易雲音樂實現版本、總不能再來一個QQMusicXiaMiMusic吧?我們建立一個Factory工廠來管理所有的音樂:


var WangyiMusicAction = function(action){

    this.vender = '網易雲音樂';
    this.playingMusic = 'see you again'

}

//為網易音樂提供共有方法
WangyiMusicAction.prototype = {
    last : function() {
        this.information = {currentMusic:'Ich will',status:'200|404',message:'上一曲'}
    },

    next : function() {
        this.information = {currentMusic:'一人我程式設計累',status:'200|404',message:'下一曲'}
    },

    play : function() {
        this.information = {currentMusic:'see you again',status:'200|500',message:'播放'}
    },

    mute : function() {
        this.information = {currentMusic:'see you again',status:'200|500',message:'靜音'}
    }

}


var QQMusicAction = function(action){

    this.vender = 'QQ音樂';
    this.playingMusic = '其實我不low'

}

//為QQ音樂提供共有方法
QQMusicAction.prototype = {
    last : function() {
        this.information = {currentMusic:'Ich will',status:'200|404',message:'上一曲'}
    },

    next : function() {
        this.information = {currentMusic:'網易才low',status:'200|404',message:'下一曲'}
    },

    play : function() {
        this.information = {currentMusic:'see you again',status:'200|500',message:'播放'}
    },

    mute : function() {
        this.information = {currentMusic:'see you again',status:'200|500',message:'靜音'}
    }

}
//音樂工廠
var MusicFactory = function(type){
    switch (type){
        case 'qq':
            return new QQMusicAction();

        case 'wangyi':
            return new WangyiMusicAction()
    }
}


var music = new MusicFactory('qq')
music.next(); //執行下一曲動作
console.log('音樂提供商 : '+music.vender);
console.log('正在播放 : '+music.playingMusic);
console.log('執行動作 : ' +music.information.message);
console.log('介面狀態 : ' +music.information.status);
console.log('執行動作後歌曲 : ' +music.information.currentMusic);

----------執行結果--------

音樂提供商 : QQ音樂
正在播放 : 其實我不low
執行動作 : 下一曲
介面狀態 : 200|404
執行動作後歌曲 : 網易才low

複製程式碼

這樣呼叫者需要用音樂介面,只需要記住MusicFactory就可以了,MusicFactory就像一個大工廠,對於music可以返回他要的一切。

好,我們回過頭來看一下:

第一種方法:是建立一個新的物件 o 對他來增強 屬性 的功能來實現的. 第二種方法:是例項化物件來建立的。 第二種方法:如果他們繼承同一個父類 BaseMusic 那麼他們父類的原型方法是可以和它們公用的! 第一種方法:我們內部 new 了一個新的個體,就不能與父類共用了.

具體哪種還是看你需求的,不過我更傾向第二種,因為他擴充套件性高,需求多的時候我們甚至可以將通用的抽離出來放到父類BaseMusic中。

在下面的繼承中我運用了類式繼承

ps:(大家可以看看建構函式繼承和組合繼承連結在最下面)
複製程式碼

//基類(父類)music方法
var BaseMusic = function(){
    this.playingMusic = 'see you again'
}
//實現通用方法
BaseMusic.prototype = {
    last : function() {
        this.information = {status:'200|404',message:'上一曲'}
    },

    next : function() {
        this.information = {currentMusic:'一人我程式設計累',status:'200|404',message:'下一曲'}
    },

    play : function() {
        this.information = {currentMusic:'see you again',status:'200|500',message:'播放'}
    },

    mute : function() {
        this.information = {currentMusic:'see you again',status:'200|500',message:'靜音'}
    }
}
//網易雲的不同於父類的構造方法
var WangyiMusicAction = function(action){

    this.vender = '網易雲音樂';

}
//這裡通過prototype實現類繼承
WangyiMusicAction.prototype = new BaseMusic();//這些動作我都放在基類了,達到程式碼複用的目的
 
//QQ
var QQMusicAction = function(action){

    this.vender = 'QQ音樂';
    this.playingMusic = '其實我不low'

}
QQMusicAction.prototype = new BaseMusic() //這些動作我都放在基類了,達到程式碼複用的目的

//音樂工廠
var MusicFactory = function(type){
    switch (type){
        case 'qq':
            return new QQMusicAction();

        case 'wangyi':
            return new WangyiMusicAction()
    }
}


var music = new MusicFactory('wangyi')
music.next(); //執行下一曲動作
console.log('音樂提供商 : '+music.vender);
console.log('正在播放 : '+music.playingMusic);
console.log('執行動作 : ' +music.information.message);
console.log('介面狀態 : ' +music.information.status);
console.log('執行動作後歌曲 : ' +music.information.currentMusic);

----------執行結果--------

音樂提供商 : 網易雲音樂
正在播放 : see you again
執行動作 : 下一曲
介面狀態 : 200|404
執行動作後歌曲 : 一人我程式設計累
複製程式碼

這樣看起來是不是更好、更簡潔呢?

建立者模式

  • 工廠模式職責:我不管你想幹啥,我只返回給你一個你想要的物件
  • 建立者模式職責:主要針對複雜業務的解耦,算是工廠的一種拆解、拼接。我可以將你的需求分解多個物件建立,更關心的是建立物件的過程。

不復雜不能突顯出他的魅力,舉個稍微複雜栗子:

我們公司是賣車的,使用者下單要買車,這個車呢:

品牌:邁巴赫、林肯、賓利、特斯拉[如果不選品牌,預設特斯拉] 
顏色:赤橙黃綠青藍紫...[如果不選顏色,預設黃色]
動力:燃油、電力、混合動力[如果不選動力,預設電力]
購買人的一些備註資訊[購買人可能會修改備註需要提供方法]
針對購買人選擇的車型返回對車型的簡單描述[描述可以修改]

複製程式碼

最終根據使用者選擇來生成一個訂單: 想下這用工廠模式是不是要寫很多的if else來返回這麼一個Car的物件呢?

我們先將 購買人的動作 反饋 分解為三個物件再在最後進行拼接 :

//建立一個汽車
var Car  = function(param){
    this.color = param && param.color || 'yellow';
    this.brand = param && param.brand || 'Tesla';
    this.power = param && param.power || 'electric';
}
//提供原型方法
Car.prototype = {
    getColor : function () {
        return this.color;
    },
    getBrand : function () {
        return this.brand;
    },
    getPower : function () {
        return this.power;
    }
}

//建立一個反饋
var FeedBack = function(brand){
    var that = this;
    (function(brand,that){
        switch (brand){
            case 'Tesla':
                // that.brand = brand;
                that.information = '特斯拉是好車'
                break
            case 'Rolls' :
                that.information = '勞斯來時是好車'
        }
    })(brand,that)
}

FeedBack.prototype.changeBrand = function (information) {
    this.information = information;
}


//建立一個顧客
var Client = function(name,message){
    this.name = name;
    this.message = message || '無留言';
}
//顧客修改備註
Client.prototype.changeMessage = function(message){
    this.message = message;
}
//然後重點在這裡!我們在這裡將我們分解的拼接起來。
var Order = function(name){
    var object = new Car();
    object.client  = new Client(name);
    object.feedBack = new FeedBack(object.brand);
    return object;
}


var orderCar = new Order('Vendar-MH');
console.log('The' + orderCar.client.name + '先生、下單一輛' + orderCar.color + '的' + orderCar.brand +' 留言內容 : ' +orderCar.client.message );
orderCar.client.changeMessage('請馬上電話聯絡我')
console.log('The' + orderCar.client.name + '先生、下單一輛' + orderCar.color + '的' + orderCar.brand +' 留言內容 : ' +orderCar.client.message );

----------執行結果--------

TheVendar-MH先生、下單一輛yellow的Tesla 留言內容 : 無留言
TheVendar-MH先生、下單一輛yellow的Tesla 留言內容 : 請馬上電話聯絡我

複製程式碼

好了,就算是關於這個訂單的更加複雜的需求,或者修改需求,不管我們多少各功能在用,我們只要微微一笑,修改下prototype等實現就好了0.0

如果您覺得還算不錯可以關注我持續看我的文章,大概方向:前後端語言設計模式如何設計好一款框架原始碼導讀技術實踐

  • 青年才俊可以入群交流:147255248

相關文章