JavaScript设计模式介绍

由于JavaScript不是典型的面向对象语言,因而在实现一些经典的设计模式上也与一般语言存在差异,本文主要介绍在JavaScript中如何实现常用的设计模式。

1. 单例模式

单例模式是最常见的设计模式,在一般的OOP语言中,我们可以通过私有化构造函数实现单例模式。但由于单例模式实际上可以看做返回的是一个结构,该结构在内存中有且仅有唯一的一份,所以可以类比JavaScript中的闭包,所以可以记住闭包完成单例模式的实现:

// 单例模式
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. 观察者模式

观察者模式下存在两个成员:观察者和被观察者。观察者在被被观察者处进行注册,当被观察者相关状态发生变化时,被观察者告知观察者,同时观察者执行相应更新逻辑。通常来说,存在多个观察者观察同一个被观察者的情况。在观察者模式下,存在以下几个组件:

  • 被观察者:维护一组被观察接口,用于添加、删除观察者,通知观察者
  • 观察者:维护一组观察者接口,用于在被观察者状态发生变化时,通知到观察者
  • 具体的被观察者:实现被观察者接口
  • 具体的观察者:实现观察者接口
// 观察者模式:建立观察者/被观察者关系,观察者可以注册其观察对象(被观察者),当被观察者的状态发生改变时,可以及时通知到观察者

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

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

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

// 观察者数量
ObserverList.prototype.Count = function() {
    return this.observerList.length;
}

// 获取某个观察者
ObserverList.prototype.Get = function(index) {
    if (index >= 0 && index < this.observerList.length) {
        return this.observerList[index];
    }
    return undefined;
}

// 删除某个观察者
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]);
// }

// 给某个对象扩展被观察者能力
function extend(extension, target) {
    for(var key in extension) {
        target[key] = extension[key];
    }
}

// 创建被观察者对象Subject,同时集成观察者对象的能力
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);
    }
};


// 构建观察者对象,主要是定义观察后的处理函数
function Observer() {
    this.Update = function() {
        //do something
    }
}

接下来我们基于观察者模式实现一个例子:

  • 一个按钮,这个按钮用于增加新的充当观察者的选择框到页面上
  • 一个控制器的选择框,充当一个被观察者,通知其他选择框是否应该被选中
  • 一个容器,用于放置新的选择框
    <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扩展被观察者能力
        extend(new Subject(), controlCheckbox);

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

        // 添加观察者
        addBtn.addEventListener('click', AddNewObserver);
        function AddNewObserver() {
            // 创建一个checkbox
            var check = document.createElement('input');
            check.type = 'checkbox';
            check.checked = controlCheckbox.checked;

            // 扩展观察者能力
            extend(new Observer(), check);
            check.Update = function(checked) {
                this.checked = checked;
            } 

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

            // 添加到容器区域
            container.appendChild(check);
        }
}

3 订阅模式

订阅模式和观察者模式很类似,都是建立观察者与被观察者之间的消息通道。观察者模式需要观察者显示的调用被观察者的观察接口来声明观察关系,从而在代码层面存在依赖关系。而订阅模式通过使用主题/事件频道将订阅者和发布者进行解耦。

// 订阅者对象
function Subscriber() {
    this.subscriberEventList = [];
}

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

// 订阅事件对象
function Subscribe(name, callback) {
    this.name = name;
    this.callback = callback;
}

// 发布事件对象
function Publish(name, context) {
    this.name = name;
    this.context = context;
}


//订阅中心对象
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. 工厂模式

工厂模式的实质由一个工厂类来代理对象(工厂模式下称为组件)的构造,组件遵循同一套组件接口,使用方只需按照工厂定制的标准将参数传递给工厂类的组件构造函数即可。工厂模式实现了组件使用方与组件之间的解耦,使得两者之间不存在显示的依赖关系,特别适合于组件众多的情况。

// 工厂模式
// 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。既可以将任意一个对象的全部和部分属性拷贝到另一个对象或类上。Mix允许对象以最小量的复杂性从外部借用(或者说继承)功能.作为一种利用Javascript对象原型工作得很好的模式,它为我们提供了从不止一个Mix处分享功能的相当灵活,但比多继承有效得多得多的方式。

// 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. 装饰模式

装饰模式动态地给一个对象增加一些额外的职责。就功能来说,Decorator模式相比生成子类更灵活,在不改变接口的前提下可以增强类的功能,在如下场景可以考虑使用装饰模式:

  • 需要扩展一个类的功能,或给一个类增加附加责任
  • 动态地给一个对象增加功能,这些功能可以再动态撤销
  • 需要增加一些基本功能的排列组合而产生的非常大量的功能,从而使继承变得 不现实

装饰模式下存在以下几个角色:

  • 抽象构件:给出一个抽象接口,以规范准备接收附加责任的对象
  • 具体构件:定义一个将要接收附加责任的类
  • 装饰角色:持有一个构件对象的实例,并定一个与抽象构件一致的接口
  • 具体装饰角色:负责给构件对象添加附加责任

[图片上传失败...(image-7703bf-1512819295760)]

相关概念可参考:设计模式——装饰模式(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() );
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,894评论 1 15
  • 接触前端两三个月的时候,那时候只是听说设计模式很重要,然后我就去读了一本设计模式的书,读了一部分,也不知道这些设计...
    艰苦奋斗的侯小憨阅读 3,019评论 2 39
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 等车时偶遇这一簇,开在小路的转弯处,想是来往车辆多尘土飞扬,枝叶多有破损,蒙尘掩色,兼久未落雨故,不见新绿,然老绿...
    幸福树阅读 292评论 0 0
  • 周一我请假在家,晚上看到组员日报火冒三丈,感觉就是我在公司和我不在公司两个样。在群里跟大家重申日报要好好写,然后实...
    Larissa阅读 324评论 0 1