JavaScript設(shè)計(jì)模式介紹

由于JavaScript不是典型的面向?qū)ο笳Z言,因而在實(shí)現(xiàn)一些經(jīng)典的設(shè)計(jì)模式上也與一般語言存在差異,本文主要介紹在JavaScript中如何實(shí)現(xiàn)常用的設(shè)計(jì)模式。

1. 單例模式

單例模式是最常見的設(shè)計(jì)模式,在一般的OOP語言中痘拆,我們可以通過私有化構(gòu)造函數(shù)實(shí)現(xiàn)單例模式。但由于單例模式實(shí)際上可以看做返回的是一個(gè)結(jié)構(gòu)氮墨,該結(jié)構(gòu)在內(nèi)存中有且僅有唯一的一份错负,所以可以類比JavaScript中的閉包,所以可以記住閉包完成單例模式的實(shí)現(xiàn):

// 單例模式
var mySingleton = (function(){
    var instance;

    init = function() {
        var privateVar = "privateVar";
        privateFunc = function() {
            console.log("This is private func");
        };
        return {
            publicVar: 'public var', // 公共變量
            publicFunc: function() { // 公共方法
                console.log('This is public func');
            },
            getPrivateVar: function() {
                return privateVar;
            }
        }
    };
  
    return {
        getInstance: function() {
            if (!instance) {
                instance = init();
            }
            return instance;
        }
    }

})();

var singleton1 = mySingleton.getInstance();
var singleton2 = mySingleton.getInstance();
singleton1.publicFunc();
console.log(singleton1 === singleton2);

2. 觀察者模式

觀察者模式下存在兩個(gè)成員:觀察者和被觀察者勇边。觀察者在被被觀察者處進(jìn)行注冊(cè),當(dāng)被觀察者相關(guān)狀態(tài)發(fā)生變化時(shí)折联,被觀察者告知觀察者粒褒,同時(shí)觀察者執(zhí)行相應(yīng)更新邏輯。通常來說诚镰,存在多個(gè)觀察者觀察同一個(gè)被觀察者的情況奕坟。在觀察者模式下,存在以下幾個(gè)組件:

  • 被觀察者:維護(hù)一組被觀察接口清笨,用于添加月杉、刪除觀察者,通知觀察者
  • 觀察者:維護(hù)一組觀察者接口抠艾,用于在被觀察者狀態(tài)發(fā)生變化時(shí)苛萎,通知到觀察者
  • 具體的被觀察者:實(shí)現(xiàn)被觀察者接口
  • 具體的觀察者:實(shí)現(xiàn)觀察者接口
// 觀察者模式:建立觀察者/被觀察者關(guān)系,觀察者可以注冊(cè)其觀察對(duì)象(被觀察者)检号,當(dāng)被觀察者的狀態(tài)發(fā)生改變時(shí)腌歉,可以及時(shí)通知到觀察者

// 被觀察者管理觀察者能力建模
function ObserverList() {
    this.observerList = [];
}

// 添加觀察者
ObserverList.prototype.Add = function(observer) {
    this.observerList.push(observer);
}

// 清空觀察者
ObserverList.prototype.Empty = function() {
    this.observerList = [];
}

// 觀察者數(shù)量
ObserverList.prototype.Count = function() {
    return this.observerList.length;
}

// 獲取某個(gè)觀察者
ObserverList.prototype.Get = function(index) {
    if (index >= 0 && index < this.observerList.length) {
        return this.observerList[index];
    }
    return undefined;
}

// 刪除某個(gè)觀察者
ObserverList.prototype.RemoveAt = function( index ){
    if( index === 0 ){
      this.observerList.shift();
    }else if( index === this.observerList.length -1 ){
      this.observerList.pop();
    }
};

// var testObserverList = new ObserverList();
// for(var key in testObserverList) {
//     console.log('key:' + key + '->' + testObserverList[key]);
// }

// 給某個(gè)對(duì)象擴(kuò)展被觀察者能力
function extend(extension, target) {
    for(var key in extension) {
        target[key] = extension[key];
    }
}

// 創(chuàng)建被觀察者對(duì)象Subject,同時(shí)集成觀察者對(duì)象的能力
function Subject() {
    this.observerList = new ObserverList();
};

Subject.prototype.AddObserver = function(observer) {
    this.observerList.Add(observer)
};

Subject.prototype.RemoveObserver = function( observer ){
    this.observers.RemoveAt( this.observers.IndexOf( observer, 0 ) );
}; 

// 通知所有觀察者
Subject.prototype.Notify = function(context) {
    var count = this.observerList.Count();
    for(var i = 0; i < count; i++) {
        this.observerList.Get(i).Update(context);
    }
};


// 構(gòu)建觀察者對(duì)象,主要是定義觀察后的處理函數(shù)
function Observer() {
    this.Update = function() {
        //do something
    }
}

接下來我們基于觀察者模式實(shí)現(xiàn)一個(gè)例子:

  • 一個(gè)按鈕齐苛,這個(gè)按鈕用于增加新的充當(dāng)觀察者的選擇框到頁面上
  • 一個(gè)控制器的選擇框翘盖,充當(dāng)一個(gè)被觀察者,通知其他選擇框是否應(yīng)該被選中
  • 一個(gè)容器凹蜂,用于放置新的選擇框
    <body>
        <button id="addNewObserver">Add New Observer checkbox</button>
        <input id="mainCheckbox" type="checkbox"/>
        <div id="observersContainer"></div>        
    </body>
<script src="./observer.js"></script> <!-- 引入上文中的js代碼 -->    

<script type="text/javascript">
        var controlCheckbox = document.getElementById('mainCheckbox');
        var addBtn = document.getElementById('addNewObserver');
        var container = document.getElementById('observersContainer');

        // 給controlCheckbox擴(kuò)展被觀察者能力
        extend(new Subject(), controlCheckbox);

        controlCheckbox.addEventListener('click', function() {
            this.Notify(this.checked);
        });

        // 添加觀察者
        addBtn.addEventListener('click', AddNewObserver);
        function AddNewObserver() {
            // 創(chuàng)建一個(gè)checkbox
            var check = document.createElement('input');
            check.type = 'checkbox';
            check.checked = controlCheckbox.checked;

            // 擴(kuò)展觀察者能力
            extend(new Observer(), check);
            check.Update = function(checked) {
                this.checked = checked;
            } 

            //添加到controlCheckbox的觀察者列表中
            controlCheckbox.AddObserver(check);

            // 添加到容器區(qū)域
            container.appendChild(check);
        }
}

3 訂閱模式

訂閱模式和觀察者模式很類似馍驯,都是建立觀察者與被觀察者之間的消息通道阁危。觀察者模式需要觀察者顯示的調(diào)用被觀察者的觀察接口來聲明觀察關(guān)系,從而在代碼層面存在依賴關(guān)系汰瘫。而訂閱模式通過使用主題/事件頻道將訂閱者和發(fā)布者進(jìn)行解耦狂打。

// 訂閱者對(duì)象
function Subscriber() {
    this.subscriberEventList = [];
}

Subscriber.prototype.addSubscribe = function(subscribe) {
    this.subscriberEventList.push(subscribe);
}

// 訂閱事件對(duì)象
function Subscribe(name, callback) {
    this.name = name;
    this.callback = callback;
}

// 發(fā)布事件對(duì)象
function Publish(name, context) {
    this.name = name;
    this.context = context;
}


//訂閱中心對(duì)象
function SubscribeCenter() {
    this.subscriberList = [];
}

SubscribeCenter.prototype.addSubscriber = function(subscriber) {
    this.subscriberList.push(subscriber);
}

SubscribeCenter.prototype.publish = function(publisher) {
    var name = publisher.name;
    var context = publisher.context;
    for(var i = 0; i < this.subscriberList.length; i++) {
        for(var j = 0; j < this.subscriberList[i].subscriberEventList.length; j++) {
            var subscribeevent = this.subscriberList[i].subscriberEventList[j];
            if(subscribeevent.name === name) {
                subscribeevent.callback.call(this.subscriberList[i], name, context);
            }
        }
    }
}

function extend(extend, obj) {
    for(var key in extend) {
        obj[key] = extend[key];
    }
}

4. 工廠模式

工廠模式的實(shí)質(zhì)由一個(gè)工廠類來代理對(duì)象(工廠模式下稱為組件)的構(gòu)造,組件遵循同一套組件接口吟吝,使用方只需按照工廠定制的標(biāo)準(zhǔn)將參數(shù)傳遞給工廠類的組件構(gòu)造函數(shù)即可菱父。工廠模式實(shí)現(xiàn)了組件使用方與組件之間的解耦,使得兩者之間不存在顯示的依賴關(guān)系剑逃,特別適合于組件眾多的情況浙宜。

// 工廠模式
// A constructor for defining new cars
function Car( options ) {
    
      // some defaults
      this.doors = options.doors || 4;
      this.state = options.state || "brand new";
      this.color = options.color || "silver";
    
    }
    
    // A constructor for defining new trucks
    function Truck( options){
    
      this.state = options.state || "used";
      this.wheelSize = options.wheelSize || "large";
      this.color = options.color || "blue";
    }
    
    // FactoryExample.js
    
    // Define a skeleton vehicle factory
    function VehicleFactory() {}
    
    // Define the prototypes and utilities for this factory
    
    // Our default vehicleClass is Car
    VehicleFactory.prototype.vehicleClass = Car;
    
    // Our Factory method for creating new Vehicle instances
    VehicleFactory.prototype.createVehicle = function ( options ) {
    
      if( options.vehicleType === "car" ){
        this.vehicleClass = Car;
      }else{
        this.vehicleClass = Truck;
      }
    
      return new this.vehicleClass( options );
    
    };
    
    // Create an instance of our factory that makes cars
    var carFactory = new VehicleFactory();
    var car = carFactory.createVehicle( {
                vehicleType: "car",
                color: "yellow",
                doors: 6 } );
    
    // Test to confirm our car was created using the vehicleClass/prototype Car
    
    // Outputs: true
    console.log( car instanceof Car );
    
    // Outputs: Car object of color "yellow", doors: 6 in a "brand new" state
    console.log( car );

    // 抽象工廠
    var AbstractVehicleFactory = (function () {
        
            // Storage for our vehicle types
            var types = {};
        
            return {
                getVehicle: function ( type, customizations ) {
                    var Vehicle = types[type];
        
                    return (Vehicle ? new Vehicle(customizations) : null);
                },
        
                registerVehicle: function ( type, Vehicle ) {
                    var proto = Vehicle.prototype;
        
                    // only register classes that fulfill the vehicle contract
                    if ( proto.drive && proto.breakDown ) {
                        types[type] = Vehicle;
                    }
        
                    return AbstractVehicleFactory;
                }
            };
        })();
        
        // Usage:
        
        AbstractVehicleFactory.registerVehicle( "car", Car );
        AbstractVehicleFactory.registerVehicle( "truck", Truck );
        
        // Instantiate a new car based on the abstract vehicle type
        var car = AbstractVehicleFactory.getVehicle( "car" , {
                    color: "lime green",
                    state: "like new" } );
        
        // Instantiate a new truck in a similar manner
        var truck = AbstractVehicleFactory.getVehicle( "truck" , {
                    wheelSize: "medium",
                    color: "neon yellow" } );

5. Mixin模式

mixin是javascript中最為常用的一種模式,幾乎所有javascript框架都用到了mixin蛹磺。既可以將任意一個(gè)對(duì)象的全部和部分屬性拷貝到另一個(gè)對(duì)象或類上粟瞬。Mix允許對(duì)象以最小量的復(fù)雜性從外部借用(或者說繼承)功能.作為一種利用Javascript對(duì)象原型工作得很好的模式,它為我們提供了從不止一個(gè)Mix處分享功能的相當(dāng)靈活,但比多繼承有效得多得多的方式。

// Define a simple Car constructor
var Car = function ( settings ) {
    
            this.model = settings.model || "no model provided";
            this.color = settings.color || "no colour provided";
    
        };
    
    // Mixin
    var Mixin = function () {};
    
    Mixin.prototype = {
    
        driveForward: function () {
            console.log( "drive forward" );
        },
    
        driveBackward: function () {
            console.log( "drive backward" );
        },
    
        driveSideways: function () {
            console.log( "drive sideways" );
        }
    
    };
    
    // Extend an existing object with a method from another
    function augment( receivingClass, givingClass ) {
    
        // only provide certain methods
        if ( arguments[2] ) {
            for ( var i = 2, len = arguments.length; i < len; i++ ) {
                receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
            }
        }
        // provide all methods
        else {
            for ( var methodName in givingClass.prototype ) {
    
                // check to make sure the receiving class doesn't
                // have a method of the same name as the one currently
                // being processed
                if ( !Object.hasOwnProperty(receivingClass.prototype, methodName) ) {
                    receivingClass.prototype[methodName] = givingClass.prototype[methodName];
                }
    
                // Alternatively:
                // if ( !receivingClass.prototype[methodName] ) {
                //  receivingClass.prototype[methodName] = givingClass.prototype[methodName];
                // }
            }
        }
    }
    
    // Augment the Car constructor to include "driveForward" and "driveBackward"
    augment( Car, Mixin, "driveForward", "driveBackward" );
    
    // Create a new Car
    var myCar = new Car({
        model: "Ford Escort",
        color: "blue"
    });
    
    // Test to make sure we now have access to the methods
    myCar.driveForward();
    myCar.driveBackward();
    
    // Outputs:
    // drive forward
    // drive backward
    
    // We can also augment Car to include all functions from our mixin
    // by not explicitly listing a selection of them
    augment( Car, Mixin );
    
    var mySportsCar = new Car({
        model: "Porsche",
        color: "red"
    });
    
    mySportsCar.driveSideways();
    
    // Outputs:
    // drive sideways

6. 裝飾模式

裝飾模式動(dòng)態(tài)地給一個(gè)對(duì)象增加一些額外的職責(zé)萤捆。就功能來說裙品,Decorator模式相比生成子類更靈活,在不改變接口的前提下可以增強(qiáng)類的功能俗或,在如下場(chǎng)景可以考慮使用裝飾模式:

  • 需要擴(kuò)展一個(gè)類的功能市怎,或給一個(gè)類增加附加責(zé)任
  • 動(dòng)態(tài)地給一個(gè)對(duì)象增加功能,這些功能可以再動(dòng)態(tài)撤銷
  • 需要增加一些基本功能的排列組合而產(chǎn)生的非常大量的功能辛慰,從而使繼承變得 不現(xiàn)實(shí)

裝飾模式下存在以下幾個(gè)角色:

  • 抽象構(gòu)件:給出一個(gè)抽象接口区匠,以規(guī)范準(zhǔn)備接收附加責(zé)任的對(duì)象
  • 具體構(gòu)件:定義一個(gè)將要接收附加責(zé)任的類
  • 裝飾角色:持有一個(gè)構(gòu)件對(duì)象的實(shí)例,并定一個(gè)與抽象構(gòu)件一致的接口
  • 具體裝飾角色:負(fù)責(zé)給構(gòu)件對(duì)象添加附加責(zé)任

[圖片上傳失敗...(image-7703bf-1512819295760)]

相關(guān)概念可參考:設(shè)計(jì)模式——裝飾模式(Decorator)

// The constructor to decorate
function MacBook() {

  this.cost = function () { return 997; };
  this.screenSize = function () { return 11.6; };

}

// Decorator 1
function Memory( macbook ) {

  var v = macbook.cost();
  macbook.cost = function() {
    return v + 75;
  };

}

// Decorator 2
function Engraving( macbook ){

  var v = macbook.cost();
  macbook.cost = function(){
    return  v + 200;
  };

}

// Decorator 3
function Insurance( macbook ){

  var v = macbook.cost();
  macbook.cost = function(){
     return  v + 250;
  };

}

var mb = new MacBook();
Memory( mb );
Engraving( mb );
Insurance( mb );

// Outputs: 1522
console.log( mb.cost() );

// Outputs: 11.6
console.log( mb.screenSize() );
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帅腌,一起剝皮案震驚了整個(gè)濱河市驰弄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌速客,老刑警劉巖戚篙,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異溺职,居然都是意外死亡岔擂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門浪耘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來智亮,“玉大人,你說我怎么就攤上這事点待±龋” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵癞埠,是天一觀的道長(zhǎng)状原。 經(jīng)常有香客問我聋呢,道長(zhǎng),這世上最難降的妖魔是什么颠区? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任削锰,我火速辦了婚禮,結(jié)果婚禮上毕莱,老公的妹妹穿的比我還像新娘器贩。我一直安慰自己,他們只是感情好朋截,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布蛹稍。 她就那樣靜靜地躺著,像睡著了一般部服。 火紅的嫁衣襯著肌膚如雪唆姐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天廓八,我揣著相機(jī)與錄音奉芦,去河邊找鬼。 笑死剧蹂,一個(gè)胖子當(dāng)著我的面吹牛声功,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宠叼,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼减噪,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了车吹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤醋闭,失蹤者是張志新(化名)和其女友劉穎窄驹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體证逻,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乐埠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了囚企。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丈咐。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖龙宏,靈堂內(nèi)的尸體忽然破棺而出棵逊,到底是詐尸還是另有隱情,我是刑警寧澤银酗,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布辆影,位于F島的核電站徒像,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蛙讥。R本人自食惡果不足惜锯蛀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望次慢。 院中可真熱鬧旁涤,春花似錦、人聲如沸迫像。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侵蒙。三九已至造虎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纷闺,已是汗流浹背算凿。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留犁功,地道東北人氓轰。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像浸卦,于是被迫代替她去往敵國(guó)和親署鸡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 設(shè)計(jì)模式匯總 一限嫌、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用靴庆、多...
    MinoyJet閱讀 3,903評(píng)論 1 15
  • 接觸前端兩三個(gè)月的時(shí)候,那時(shí)候只是聽說設(shè)計(jì)模式很重要怒医,然后我就去讀了一本設(shè)計(jì)模式的書炉抒,讀了一部分,也不知道這些設(shè)計(jì)...
    艱苦奮斗的侯小憨閱讀 3,025評(píng)論 2 39
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法稚叹,類相關(guān)的語法焰薄,內(nèi)部類的語法,繼承相關(guān)的語法扒袖,異常的語法塞茅,線程的語...
    子非魚_t_閱讀 31,581評(píng)論 18 399
  • 等車時(shí)偶遇這一簇,開在小路的轉(zhuǎn)彎處季率,想是來往車輛多塵土飛揚(yáng)野瘦,枝葉多有破損,蒙塵掩色飒泻,兼久未落雨故缅刽,不見新綠啊掏,然老綠...
    幸福樹閱讀 293評(píng)論 0 0
  • 周一我請(qǐng)假在家,晚上看到組員日?qǐng)?bào)火冒三丈衰猛,感覺就是我在公司和我不在公司兩個(gè)樣迟蜜。在群里跟大家重申日?qǐng)?bào)要好好寫,然后實(shí)...
    Larissa閱讀 324評(píng)論 0 1