原创翻译:《JavaScript design patterns – Part 1: Singleton, composite, and façade》 – Javascript 设计模式(单例模式、复合模式和外观模式);
作者:微博@IMAGINE3R
原文地址:https://www.adobe.com/devnet/html5/articles/javascript-design-patterns-pt1-singleton-composite-facade.html


要求
必备知识:
基本JavaScript编程知识
其他要求 (开源库使用)
Query库
译者提示:
这是一篇关于“javascript面向对象程序设计”的文章,注意,不是“javascript交互设计模式”


这是”javascript通用设计模式”系列的的第一篇文章。设计模式是最有效的编程手段,它让你的程序代码更易维护、更好扩展、更独立而不容易冲突,所有这些对于”创建一个大型javascript应用(尤其在大型公司的大型项目中)”是很有必要的。

单例模式

利用单例模式保证一个对象的只创建了一个实例,实现资源整合。在传统的面向对象的编程语言中,有“类”的概念,这个“类”可能同时拥有静态和非静态的属性和方法,所以单例在这里显得有些复杂。而对于Javascript这个动态语言来讲,它没有真正意义上“类”的概念,这样,单例到Javascript就显得清晰而且简单了很多。
为什么需要单例呢?

在进行详细部署之前,我需要讨论单例是否对我的应用有用。如果能够确保一个对象只有一个实例,这就大大降低了复杂度而且更方便使用。比如,在服务器端,为一个请求开通一个以上的数据接口是一种浪费,因此你需要用一个单例去控制一个数据接口。

同样,比如,在客户端的Javascript中,你也许会把一个用来处理所有的AJAX请求的对象作为一个单例进行打包。这里有一个简单的规则:当你需要创建一个新的实例的时候,如果在功能上和其他实例完全一样,创建一个单例把它们包含起来。

如何创建一个单例
你需要做的就是用”对象字面量表示法”(Object Literal Notation)创建一个对象:

//Object Literal Notation的中文翻译有很多,比如标题写的对象自变量表示法,又或者文字对象表示法等等,总觉得原来的英文术语翻译成中文就会经历一下“演绎”的过程,变得过于“专业”和“深奥”。好多时候这样的中文术语还不如英文原词来的轻松。接下来的文章就用Object Literal来表示。


var Singleton = {
prop: 1,
another_prop: 'value',
method: function() {…},
another_method: function() {…}
};

你还可以创建私有属性和方法的单例,如果涉及闭包或匿名的自调用函数,就会复杂一些:例如,在一个功能函数里,先声明那些函数和变量,然后,创建并返回到一个object literal,引用之前在父函数范围内声明的那些函数和变量,这样,在功能完成声明后,object literal就会被指向一个变量时,这时外部函数就会立即被替换并执行。说起来有些含糊,还是来看一个例子:

var Singleton = (function() {
var private_property = 0,
private_method = function () {
console.log('This is private');
}

return {
prop: 1,
another_prop: ‘value’,
method: function() {
private_method();
return private_property;
},
another_method: function() {…}
}
}());

最关键的是:
1、变量要在object literal之前用var关键字声明并包含在一个函数内(确保它是内部变量而且可以在object literal中被引用);
2、随着之前的内部函数的执行,并返回到object literal,得到了最终的单例。

单例的命名空间
在Javascript中,命名空间是通过把对象最为属性添加给另一个对象,这就意味着对象可能有多层结构。这有助于有效地组织代码到不同的逻辑层。YUI的javascript库把层级做的很深,伴随有相当多层的命名空间,我觉得他们做的有些过。一般情况,也是在大部分的最佳实践告诉我们的,最好限制命名空间的层级到几个或者更少。下面的代码就是命名空间的例子:

var Namespace = {
Util: {
util_method1: function() {…},
util_method2: function() {…}
},
Ajax: {
ajax_method: function() {…}
},
some_method: function() {…}
};

// Here’s what it looks like when it’s used
Namespace.Util.util_method1();
Namespace.Ajax.ajax_method();
Namespace.some_method();

我曾经说过,对于使用命名空间,全局变量要尽可能短。还有,假如你有雄心大略,你可以把整个应用附加到一个命名对象,你可以把它命名为“app”(前提是你对你的应用有全局的把握)。
如果你想阅读更多关于单元集设计模式的文章,以及单元集在命名空间方面的适用性,你可以去我的个人博客阅读更多内容。JavaScript Design Patterns: Singleton

复合模式

如果你看完了单元集模式,心想:“挺简单的嘛”。别着急,我还有更复杂的设计模式要讨论,复合模式就是其中之一。复合模式从字面上讲,就是一个对象,它是一个由很多部分组成的一个独立的整体,这个整体作为各部分的共同接口,简单的说它也是一个复合容器,也不知道他具体又多少部分组成,所以页具有欺骗性。
复合模式的结构

解释复合模式最好的方法就是利用图示。在图一中,你可以看到两个不同类型的对象:containers和galleries是复合模式,而images是独立的“枝叶”。一个复合模式可以容纳子类,但是一般情况不执行太多事件。“枝叶”一般包含了大部分的事件,但是不能拥有子类,至少在典型的复合模式里面不可以。

图一:复合模式模型

在另一个例子里,我100%肯定你一直在接触复合模式,但是从来不会意识到它。例如,电脑里的文件结构。如果你删除了一个文件夹,它下面的所有内容和文档将被同时删除,没错吧。这就是复合模式的工作方式。你可以调用一个复合对象的方法,这个信息将会被传递到它的层级体系中去。

复合模式代码示例

下面这段代码创建了一个相册,它是一个复合模式的实例。你将会三个层次:album(相册簿)、gallery(相片廊)、image(相片)。album和gallery是复合层(文件夹),图片是“枝叶层”(文件),就像图一展示的那样,不过这个例子更加具体,而且仅仅把层级结构分为“复合层”和“枝叶层”。一个标准的复合模式不会限制层级数量,更不会指定哪一层必须是枝叶层。

开始,你需要创建一个名叫“GalleryComposite”的类,它可以同时被album和galleries使用。注意,我这里为了简化,用了jQuery来控制DOM。

var GalleryComposite = function (heading, id) {
this.children = [];

this.element = $(‘

‘)
.append(‘

‘ + heading + ‘

‘);
}

GalleryComposite.prototype = {
add: function (child) {
this.children.push(child);
this.element.append(child.getElement());
},

remove: function (child) {
for (var node, i = 0; node = this.getChild(i); i++) {
if (node == child) {
this.children.splice(i, 1);
this.element.detach(child.getElement());
return true;
}

if (node.remove(child)) {
return true;
}
}

return false;
},

getChild: function (i) {
return this.children[i];
},

hide: function () {
for (var node, i = 0; node = this.getChild(i); i++) {
node.hide();
}

this.element.hide(0);
},

show: function () {
for (var node, i = 0; node = this.getChild(i); i++) {
node.show();
}

this.element.show(0);
},

getElement: function () {
return this.element;
}
}

写了一大段啊,让我来解释一下吧。其中定义的add、remove、getChild方法是用来组建复合对象的。在这个实例中,可能不会用到remove和getChild这两个方法,但是他们对于创建一个动态的复合对象是非常有用的。其中的hide、show、和getElement等方法是用来控制DOM的。这个复合模式是用来在页面上向用户展示一个动态画册,可以控制gallery元素的显隐:如果隐藏了album,整个相册将会消失;如果只是隐藏一张图片,只有这张图片会消失。

现在来创建GalleryImage“类”,它将使用和GalleryComposite相同的方法,换句话说,他们所采用的接口是相同的,唯独image是一个“枝叶”,它实际上和与子元素相关的方法无关,因为它根本没有子元素。采用相同的接口是复合模式工作的必要条件,因为一个复合元素不知道自己是不是增加了另一个复合元素或“枝叶”,它只能通过访问它子元素的方法来判断。这样才能需要确保没有错误发生。

var GalleryImage = function (src, id) {
this.children = [];

this.element = $(‘‘)
.attr(‘id’, id)
.attr(‘src’, src);
}

GalleryImage.prototype = {
// Due to this being a leaf, it doesn’t use these methods,
// but must implement them to count as implementing the
// Composite interface
add: function () { },

remove: function () { },

getChild: function () { },

hide: function () {
this.element.hide(0);
},

show: function () {
this.element.show(0);
},

getElement: function () {
return this.element;
}
}

到这一步,你可以用这些复合对象,建立这些对象的prototype。下面是建立这个相册的实际代码。

var container = new GalleryComposite('', 'allgalleries');
var gallery1 = new GalleryComposite('Gallery 1', 'gallery1');
var gallery2 = new GalleryComposite('Gallery 2', 'gallery2');
var image1 = new GalleryImage('image1.jpg', 'img1');
var image2 = new GalleryImage('image2.jpg', 'img2');
var image3 = new GalleryImage('image3.jpg', 'img3');
var image4 = new GalleryImage('image4.jpg', 'img4');

gallery1.add(image1);
gallery1.add(image2);

gallery2.add(image3);
gallery2.add(image4);

container.add(gallery1);
container.add(gallery2);

// Make sure to add the top container to the body,
// otherwise it’ll never show up.
container.getElement().appendTo(‘body’);
container.show();

复合模式到这里就告一段落了。你可以:观看这个相册示例的DEMO;或者可以访问我的博客了解更多。JavaScript Design Patterns: Composite

外观模式

这是我要在这篇文章中介绍的最后一个设计模式 – 外观模式。外观模式只是一个单独的一段代码,这段代码的要做的是简化复杂的接口设置。不得不承认,程序中很多函数都不是实际的功能,而都是在做“简化接口”,这是很常见的一种做法,也是很有必要的设计模式。外观模式的初衷就是为了把大规模的逻辑段转化成一个响应接口,可以简单调用。

外观模式的示例
你可能都没有意识到,其实你一直在用外观模式在设计程序。你所用到的一些小型的程序语言库或多或少都是利用外观模式开发出来的,因为也许这个代码库的开发目的就是把复杂的事情变的更加简单。

我们再来看看jQuery,它的一个简单的函数可以做很多事情,比如jQuery的工厂函数jquery(),它可以查找DOM、创建元素、或者仅仅是把DOM元素转化为jQuery对象。拿查找元素来说,jQuery只需要很短的代码就可以实现,你再也不用写一长串复杂的代码。你在利用jQuery的同时就是在利用外观模式简化你的代码,使复杂的功能简单调用,也更加方便不是吗?!
结束
外观模式很容易理解,如果你想了解更多,可以上我的博客踩踩。JavaScript Design Patterns: Façade

继续学习

这是javascript设计模式系列的第一篇文章,涵盖了单例模式、复合模式和外观模式。在这个系列接下来的文章中,会涉及到另外三种设计模式,概念可能更加抽象。如果你有耐心,可以去我的个人博客,在那里你可以看到这个系列的所有文章。

备注:对于某些概念,中文翻译的意思可能不准确,如果你有一定的英文基础,建议不要参照翻译。
概念翻译参考:
Singleton – 单例模式
Composite – 复合模式
Façade – 外观模式