yizihan / blog

Record
1 stars 0 forks source link

设计模式 - 上 #13

Open yizihan opened 6 years ago

yizihan commented 6 years ago

设计模式

设计模式(Design pattern)是一套被反复使用、思想成熟、经过分类和长期实战设计经验总结的。 使用设计模式是为了让代码可重用、可扩展、可解耦、更容易被人理解且能保证代码可靠性。设计模式使代码开发真正工程化。

设计原则

设计模式存在根本原因是为了代码复用,增加可维护性。有如下原则:


构造器模式 - Constructor Pattern

Constructor是一种在内存已分配给该对象的情况下,用于初始化新创建对象的特殊方法。 在JavaScript中,几乎所有的东西都是对象,我们通常最感兴趣的是object构造器。

// object构造器
var newObject = new Object();

带原型的Constructor

function Car(model, year, mails) {
    this.model = model;
    this.year = year;
    this.mails = mails;
}
Car.prototype.detail = function () {
   return this.model + "(" + this.year + ")" + " has done " + this.mails;
}
var civic = new Car('Honda Civic', '2017', '5000');
console.log(civic.detail())

使用构造函数,然后通过new的方式创建基于构造函数的新实例;实例们共享的方法存放在构造函数的原型上。


模块模式 - Module Pattern

模块模式能够帮助我们清晰地分离和组织项目中的单元代码。 模块模式最初被定义为在传统软件工程中为类提供私有和公有的封装方法。 该模式除了返回一个对象而不是一个函数之外,非常类似于一个立即调用的函数表达式。

通过创建自包含来实现Module Pattern。

var newModule = (function() {
    // 私有变量
    var count = 0;
    return {
        add: function() {
            return ++count;
        },
        reset: function() {
            return count = 0;
        }
    }
})();
newModule.add();

引入混入

将全局变量作为参数传递给模块的匿名函数。

// 引入jQuery和Underscore
var myModule = (function(jQuery, _) {
    ...
})(jQuery, _);

单例模式 - Singleton Pattern

单例模式限制了类的实例化只有一次。 在JavaScript中,Singleton充当共享资源命名空间,从全局命名空间中隔离出代码实现,从而为函数提供单一访问点。

var Singleton = (function() {
    var instance = null;
    function init() {
        // 私有变量
        var privateVar = 'yizihan';
        return {
            // 公有方法
            publicFunc: function() {
                console.log(privateVar);
            }
        }
    }
    return {
        // 获取Singleton的实例,如果存在就返回,不存在就创建新实例
        getInstance: function() {
            if(!instance) {
                instance = init();
            }
            return instance;
        }
    }
})();
Singleton.getInstance().publicFunc();   // yizihan
var singleA = Singleton.getInstance();
var singleB = Singleton.getInstance();
console.log(singleA.publicFunc() === singleB.publicFunc());     // true

通过Singleton模式创建的实例指向同一个位置。


观察者模式 - Observer Pattern

观察者模式:一个对象(Subject)维持一系列依赖于它的对象(Observer),将目标对象(Subject)有关状态的任何变更自动通知(Subject.Notify())给观察者(Observer),而观察者根据通知执行相应的更新(Observer.Update()

// 创建观察者列表 构造函数
function ObserverList() {
    this.observerList = [];
}
// 观察者列表相关函数
ObserverList.prototype.Add = function(obj) {
    return this.observerList.push(obj)
}
ObserverList.prototype.Get = function(index) {
    if(index > -1 && index < this.observerList.length) {
        return this.observerList[index]
    }               
}
ObserverList.prototype.Count = function() {
    return this.observerList.length;
}

// 目标对象 构造函数
function Subject() {
    // 目标上的观察者
    this.observers = new ObserverList();    // 空数组
}
// 添加观察者
Subject.prototype.AddObserver = function(observer) {
    this.observers.Add(observer);       // 向observerList数组添加元素
}
// 遍历所有观察者并调用他们的Update()
Subject.prototype.Notify = function(context) {
    var observerCount = this.observers.Count();
    for(var i = 0; i < observerCount; i++) {
        // 调用Observer.Update()
        this.observers.Get(i).Update(context)
    }
}

// 观察者 构造函数
function Observer() {
    this.Update = function() {}
}

// 扩展功能函数 为对象(extension)扩展目标对象(obj)的所有属性
function extend(obj, extension) {
    for(var key in obj) {
        extension[key] = obj[key];
    }
}

示例

<button id="addBtn">Add New Oberver checkbox</button>
<input id="controlCheckbox" type="checkbox">
<div id="observersContainer">Container:</div>
<script>
var addBtn = document.getElementById('addBtn'),
    controlCheckbox = document.getElementById('controlCheckbox'),
    container = document.getElementById('observersContainer');

// ConcreteSubject 具体目标 将复选框设为目标
extend(new Subject(), controlCheckbox);

// 添加checkbox复选框 并为controlCheckbox添加观察者
addBtn['onclick'] = addNewObserver;

function addNewObserver() {
    var check = document.createElement('input');
    check.type = 'checkbox';
    // 声明当前生成的复选框为观察者 ConcreteObserver
    extend(new Observer(), check);
    // 重新定义更新行为
    check.Update = function(value) {
        this.checked = value;
    }
    // 为control添加观察者
    controlCheckbox.AddObserver(check);
    container.appendChild(check);
}

// 点击checkbox会触发Notify()通知到所有的观察者,并调用观察者的Update()
controlCheckbox['onclick'] = new Function('controlCheckbox.Notify(controlCheckbox.checked)');
</script>

发布/订阅模式 - Publish/Subscribe Pattern

Publish/Subscribe 模式使用了一个主题/事件通道,这个通道介于希望接收到通知(订阅者)的对象激活事件的对象(发布者)之间。

通过订阅另一个对象的特定任务或活动,当任务或活动发生时获得通知,而不是单个对象直接调用其他对象的方法。

var pubsub = {};
(function(pubsub) {
    // 公共数据
    var topics = {},    // 主题集合
            subUid = -1;    // 用于生成编号

    // 发布者 发布一个topic,如果该topic有订阅者,则通知所有的订阅者,并调用自己的回调函数
    pubsub.publish = function(topic, args) {    // 发布主题和相关数据
        // 如果发布的主题不在主题集合中
        if(!topics[topic]) return false;    

        // 该主题的订阅者
        var subscribers = topics[topic],                // topics[topic]是一个数组
                len = subscribers ? subscribers.length : 0;

        while(len--) {
            // 遍历订阅者,然后调用自身的func函数
            subscribers[len].func(topic, args)      // 调用每个订阅者自定义的函数
        }
        return this;
    };

    // 订阅者 订阅一个topic,同时设置该topic发布时的回调函数
    pubsub.subscribe = function(topic, func) {  // 订阅主题和相关函数
        // 如果订阅的主题不存在,则新建此主题
        if(!topics[topic]) {
            topics[topic] = [];
        }

        // 订阅者的编号
        var token = (++subUid).toString();  
        topics[topic].push({        // 向主题添加对象,及相关的函数,且函数可以不同
            token: token,
            func: func
        });
        return token;                       // 得到当前订阅者的编号
    };

    pubsub.unsubscribe = function(token) {
        for (var m in topics) {     // 遍历主题集合
            if(topics[m]) {
                for (var i = 0; i < topics[m].length; i++) {    // 遍历当前主题的所有订阅者
                    if(topics[m][i].token === token) {                  // 判断订阅者所携带的token
                        topics[m].splice(i, 1);                                     // 从当前主题中删除
                        return token;                                                           // 返回被删除的订阅者编号
                    }
                }
            }
        }
        return this;        // this指向pubsub
    };
})(pubsub);

// 发生发布时的回调
var messageLogger = function(topic, data) {
    console.log('Logging: ' + topic + ': ' + data);
}

// 完成订阅
var sub1 = pubsub.subscribe('inbox/newMessage', messageLogger);
console.log(sub1);      // 0 

// 发布信息
pubsub.publish('inbox/newMessage', 'hello world');      // Logging: inbox/newMessage: hello world

// 取消订阅
console.log(pubsub.unsubscribe(sub1));          // 通过编号取消指定订阅者
pubsub.publish('inbox/newMessage', 'hello bye');

原型模式 - Prototype Pattern

原型模式是一种基于现有对象模板,通过克隆方式创建对象的模式。 Prototype模式是基于原型继承的模式,可以在其中创建对象,作为其他对象的原型。

var Car = {
    getModel: function() {
        console.log("The firstname of this car is " + this.firstname);
    }
}
// Object.creat允许我们使用第二个参数来初始化Civic对象的属性
var Civic = Object.create(Car, {
    'id': {
        value: 0,
        enumerable: true
    },
    'firstname': {
        value: 'Honda',
        enumeable: true
    }
});
Civic.getModel();   // The firstname of this car is Honda

性能提升:所有通过Object.create()生成的对象,它们继承的函数方法都指向父类中的此函数,而不是创建它们自己的单份拷贝。

Object.create()

Object.create = function(obj) {
    function Func() {};     // 设置构造器
    Func.prototype = obj;   // 设置构造器的原型指向
    return new Func();      // 返回生成的新对象
} 

工厂模式 - Factory Pattern

工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型(抽象工程)。

Factory可以通过一个通用的接口来创建对象,我们可以指定所创建的工厂对象的类型。

// 汽车类
function Car(options) {
    this.doors = options.doors || 4;
    this.state = options.state || 'brand new';
    this.color = options.color || 'silver';
}
// 卡车类
function Truck(options) {
    this.state = options.state || 'used';
    this.wheelSize = options.wheelSize || 'large';
    this.color = options.color || 'yellow';
}

// 车辆工厂 构造函数
function VehicleFactory() { }
// 原型属性vehicleClass指向某个类  车辆工厂默认生产 汽车类
VehicleFactory.prototype.vehicleClass = Car;
// 车辆生产方法 根据输入的车辆类型,判断生产哪种车辆
VehicleFactory.prototype.createVehicle = function(options) {
    if(options.vehicleType === 'car') {
        this.vehicleClass = Car;
    } else {
        this.vehicleClass = Truck;
    }
    // 返回相应构造函数生成的实例
    return new this.vehicleClass(options);
    // new Car(options)
}

// 汽车工厂实例
var carFactory = new VehicleFactory();
// 汽车工厂调用车辆生产方法  指定类型为car
var car = carFactory.createVehicle({
    vehicleType: 'car',
    color: 'yellow',
    doors: 6
});
console.log(car instanceof Car);    // true
console.log(car);                   // Car {doors: 6, state: "brand new", color: "yellow"}

何时使用Factory

何时不应使用Factory

除非为创建对象提供一个接口是我们编写库的设计目标,否则建议使用显示构造函数,以避免不必要的开销。

抽象工厂

// 汽车类
function Car(options) {
    this.doors = options.doors || 4;
    this.state = options.state || 'brand new';
    this.color = options.color || 'silver';
}
// 车辆成产契约
Car.prototype.drive = true;
Car.prototype.breakDown = true;

var AbstructVehicleFactory = (function() {
    var types = {};

    return {
        getVehicle: function(type, options) {
            // 根据控制中心是否存在指定车辆类 
            var VehicleType = types[type];
            // 返回生成的指定车辆类的实例
            return (VehicleType) ? new VehicleType(options) : null;
        },
        // 注册车辆 将指定的车辆类添加到 控制中心types
        registerVehicle: function(type, vehicleClass) {
            var proto = vehicleClass.prototype; // 获得原型
            // 只注册实现车辆契约的类 
            if(proto.drive && proto.breakDown) { 
                types[type] = vehicleClass;
            }
            return AbstructVehicleFactory
        }
    }

})();
// 先完成车辆的注册
AbstructVehicleFactory.registerVehicle('car', Car);
// 然后再生产车辆
var car = AbstructVehicleFactory.getVehicle('car', {
    color: 'green',
    state: 'new'
});
console.log(car);       // Car {doors: 4, state: "new", color: "green"}