灏天阁

设计模式

· Yin灏
  • 单件模式
  • 工厂模式
  • 装饰器模式
  • 观察者模式

单件模式

一个类只能被创建一个实例对象,如果之后在尝试创建该对象的话,代码只会返回原来的实例。

var my_log = new Logger();

//... 1000 次

var other_log = new Logger();
console.log(my_log === other_log); // true

如何实现的呢?

全局变量

解决方案之一就是用全局变量来保存这个唯一的实例。

function Logger() {
    if(typeof global_log === "undefined") {
        global_log = this;
    }
    return global_log;
}

var a = new Logger();
var b = new Logger();
a === b; // true

这样做的缺陷也正是使用了全局变量,他在任何时候都有被覆盖的可能,从而导致实例丢失,反之亦然,迁全局变量也随时可能覆盖掉别的对象。

构造器属性

函数也是一个对象,本身也是有属性的,因此,可以将这个唯一实例设置为构造器本身的属性。

function Logger() {
    if(!Logger.single_instance) {
        Logger.single_instance = this;
    }
    return Logger.single_instance;
}

这个方法显然解决了全局变量所带来的问题,因为没有全局变量被创建,它唯一的缺陷是 Logger 构造器的属性是公有的,因此随时可能被覆盖。

最好的方式是使用私有属性。

工厂模式

当我们有多个相似的对象而又不知道应该先使用哪种时,就可以考虑使用工厂模式,在该模式下,代码将会根据具体的输入或其他既定规则,自行决定创建哪种类型的对象。

var MYAPP = {};

MYAPP.dom = {};

MYAPP.dom.Text = function(url) {
    this.url = url;
    this.insert = function(where) {
        var txt = document.createTextNode(this.url);
        where.appendChild(txt);
    }
}

MYAPP.dom.Link = function(url) {
    this.url = url;
    this.insert = function(where) {
        var link = document.createElement('a');
        link.href = this.url;
        link.appendChild(document.createTextNode(this.url));
        where.appendChild(link);
    }
}

MYAPP.dom.Image = function(url) {
    this.url = url;
    this.insert = function(where) {
        var im = document.createElement('img');
        im.src = this.url;
        where.appendChild(im);
    }
}

var url = 'https://www.googleapis.com/';

MYAPP.dom.factory = function(type, url) {
    return new MYAPP.dom[type](url)
}

var image = MYAPP.dom.factory('Image', url);
image.insert(document.body)

装饰器模式

它与对象的创建无关,主要考虑的是如何拓展对象的功能。

  • 装饰一棵圣诞树
var tree = {};

tree.decorate = function() {
    console.log('Make sure the tree won\' fail.');
}

// 定义 getDecorator() 方法,该方法用于添加额外的装饰器,装饰器被实现为构造器函数,都继承自 tree 对象
tree.getDecorator = function(deco) {
    tree[deco].prototype = this;
    return new tree[deco];
}

// 创建第一个装饰器 RedBalls() 
tree.RedBalls = function() {
    this.decorate = function() {
        this.RedBalls.prototype.decorate(); // 先调用父类的 decorate()
        console.log('Put on some red balls');
    }
}

tree.BlueBalls = function() {
    this.decorate = function() {
        this.BlueBalls.prototype.decorate(); // 先调用父类的 decorate()
        console.log('Add blue balls');
    }
}

tree.Angle = function() {
    this.decorate = function() {
        this.Angle.prototype.decorate(); // 先调用父类的 decorate()
        console.log('An angle on the top');
    }
}

// 再把装饰器添加到基础对象中
tree = tree.getDecorator('BlueBalls')
tree = tree.getDecorator('Angle')
tree = tree.getDecorator('RedBalls')
tree.decorate()

观察者模式

有时也称发布订阅模,主要用于处理不同对象之间的交互通信问题,观察中通常会包含两类对象

  • 一个或多个发布对象:当有重要事情发生时,会通知订阅者
  • 一个或多个订阅者对象:他们随着一个或多个发布者,监听它们的通知,并做出相应的反应
// subscribers 订阅者
var observer = {
    // 添加订阅者
    addSubscriber: function(callback) {
        if (typeof callback === 'function') {
            this.subscribers[this.subscribers.length] = callback;
        }
    },
    // 删除订阅者
    removeSubscriber: function(callback) {
        for (var i = 0; i < this.subscribers.length; i++) {
            if (this.subscribers[i] === callback) {
                delete this.subscribers[i];
            }
        }
    },
    // 授权并传递数据给订阅者
    publish: function(what) {
        for (var i = 0; i < this.subscribers.length; i++) {
            if (typeof this.subscribers[i] === 'function') {
                this.subscribers[i](what);
            }
        }
    },
    // 将任意对象转变为一个发布者并为其添加上述方法
    make: function(o) {
        for (var i in this) {
            if (this.hasOwnProperty(i)) {
                o[i] = this[i];
                o.subscribers = [];
            }
        }
    }
}

接下来创建订阅者,订阅者可以是任意对象,它们的唯一职责是在某些重要事件发生时调用 publish() 方法,

下面是一个 blogger 对象,每当新博客准备好时,就会调用 publish() 方法。

// subscribers 订阅者
var observer = {
    // 添加订阅者
    addSubscriber: function(callback) {
        if (typeof callback === 'function') {
            this.subscribers[this.subscribers.length] = callback;
        }
    },
    // 删除订阅者
    removeSubscriber: function(callback) {
        for (var i = 0; i < this.subscribers.length; i++) {
            if (this.subscribers[i] === callback) {
                delete this.subscribers[i];
            }
        }
    },
    // 授权并传递数据给订阅者
    publish: function(what) {
        for (var i = 0; i < this.subscribers.length; i++) {
            if (typeof this.subscribers[i] === 'function') {
                this.subscribers[i](what);
            }
        }
    },
    // 将任意对象转变为一个发布者并为其添加上述方法
    make: function(o) {
        for (var i in this) {
            if (this.hasOwnProperty(i)) {
                o[i] = this[i];
                o.subscribers = [];
            }
        }
    }
}

// 创建订阅者
// 下面是一个 blogger 对象,每当新博客准备好时,就会调用 publish() 方法。
var blogger = {
    writeBlogPost: function() {
        var content = 'Today is ' + new Date();
        this.publish(content);
    }
}

// 另有一个 la_time 对象,每当新一期的报刊出来时,就会调用 publish() 方法。
var la_time = {
    newIssue: function() {
        var paper = 'Margians have landed on Earth';
        this.publish(paper)
    }
}

// 他们很容易转变成发布者
observer.make(blogger);
observer.make(la_time);

console.log(blogger);
console.log(la_time);

//  准备两个简单的对象
var jack = {
    read: function(what) {
        console.log('I just read that ' + what);
    }
}

var jill = {
    gossip: function(what) {
        console.log('You didn\'t hear if from me, but ' + what);
    }
}

// 他们可以订阅 blogger 对象,只需要提供时间发生时的回调函数
blogger.addSubscriber(jack.read);
blogger.addSubscriber(jill.gossip);

// 当 blogger 写了新博客,jack 和 jill 会收到通知
blogger.writeBlogPost();

/*
  I just read that Today is Thu Dec 16 2021 13:20:11 GMT+0800 (中国标准时间)
  You didn't hear if from me, but Today is Thu Dec 16 2021 13:20:11 GMT+0800 (中国标准时间)
*/

// 取消 jill 订阅
blogger.removeSubscriber(jill.gossip)

blogger.writeBlogPost();
/*
  I just read that Today is Thu Dec 16 2021 13:24:32 GMT+0800 (中国标准时间)
*/

// jill 订阅 LA times, 一个订阅者可以对应多个发布者

la_time.addSubscriber(jill.gossip)

la_time.newIssue(); // You didn't hear if from me, but Margians have landed on Earth

- Book Lists -