设计模式
·
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