本文主要通过一个Windows文件目录结构的例子来阐述组合模式。
问题引入
Windows文件目录结构就像一棵树,有根节点(我的电脑),然后有子节点(如C盘、D盘等),子节点也还有子节点(盘符下的文件夹),文件夹下还可能会有文件夹,也可能会有单独的文件,于是,在树的概念中, 我们称单独的文件节点为叶子,而文件夹则为树枝。
于是,这一次有这样一个命题,我们希望能够有选择地隐藏或显示Windows目录结构中的特定部分。这可能是单独的文件,也可能是文件夹。考虑到Windows文件目录结构的复杂性,这里我们只关注文件或文件夹的显示和隐藏操作。
组合模式
类的定义
针对上面的命题,我们需要设计两个类,一个用作文件夹的组合对象类,一个用作文件本身的叶子对象类。
//文件夹接口:组合对象 var IDirectory = new Interface("IDirectory",["add","delete","getFile"]); //文件接口:叶子对象 var IFile = new Interface("IFile",["show","hide"]);
接下来就是非常关键的了,这里即将定义一个动态的文件系统类DynamicFileSystem,该类是IDirectory和IFile的实现类。也就是说,通过DynamicFileSystem类实例化出来的对象,既可能是一个文件,也可能是一个文件夹。
其代码如下:
//文件系统类 var DynamicFileSystem = function(id,fileName){ this.files = []; //子文件列表(注意,文件夹也是一个文件) this.createDate = new Date();//文件的创建日期 this.fileName = fileName; //文件名 //为了在WEB中很好的来描述这个问题,我们需要用到HTMLDOMElement this.element = document.createElement("div"); this.element.id = id; }; //实现两个接口 implements(DynamicFileSystem,IDirectory,IFile); //实现所有的抽象方法 DynamicFileSystem.prototype = { add : function(fileSystemChild){ this.files.push(fileSystemChild); this.element.appendChild(fileSystemChild.getElement()); }, delete : function(fileSystemChild){ for(var file,i = 0;node = this.getFile(i),i++){ if(file == fileSystemChild){ this.files.splice(i,1); break; } } this.element.removeChild(fileSystemChild.getElement()); }, getFile : function(index){ return this.files[index]; }, show : function(){ //显示文件或文件夹 this.element.style.display = "block"; for(var file,i = 0;node = this.getFile(i),i++){ file.show(); //递归调用 } }, hide : function(){ for(var file,i = 0;node = this.getFile(i),i++){ file.hide(); //递归调用 } //隐藏文件或文件夹 this.element.style.display = "none"; }, //拓展方法 getElement : function(){ return this.element; } };
在上面的代码中,首先定义的是组合对象类和叶子对象类应该实现的接口。除了常规的组合对象方法外,这些类要定义的操作只包含hide和show。 接下来定义的是组合对象类。由于DynamicFileSystem只是对div对象的包装,所以文件系统可以再嵌套子文件系统,而我们因此也只需要用到一个组合对象类即可。
实例化演示
下面就是大家迫不及待想要看到的,如何实例化并操作整个Windows的目录树结构呢?
其实到这里还没有完,我们必须要有一个真正的文件实体类,比如Word文档、PDF文档、Text文档等等,它们都是File,因此我们可以随便的定义一个文件类,并让它继承DynamicFileSystem类即可(因为它同样具备文件系统的功能), 下面以Text文档为例,创建TextFile类:
var TextFile = function(id,fileName){ //文件名 this.fileName = fileName; this.element = document.createElement("div"); this.element.id = id; }; //实施继承 inherits(TextFile,DynamicFileSystem);
如此一来,TextFile类就已经具备了DynamicFileSystem类的所有方法,而且还有一个自己独特的构造器,接收一个参数----文件名。
接下来就是真正的开始应用了,我们首先创建一个文件系统(文件夹)的实例,再创建三个文本文件TextFile类的实例,最后将这三个文本文件添加到文件系统中去:
//创建文件夹 var fileSystem = new DynamicFileSystem("dirRoot","C盘"); //创建文本文件 var txtFile1 = new TextFile("txtFile1","文本文件1"); var txtFile2 = new TextFile("txtFile2","文本文件2"); var txtFile3 = new TextFile("txtFile3","文本文件3"); //将三个文本文件添加到文件夹中 fileSystem.add(txtFile1); fileSystem.add(txtFile2); fileSystem.add(txtFile3);
在这个时候你可能想说了,我想在C盘下建立两个文件夹,分别是“Windows”和“Program Files”,该怎么办呢?很简单,继续之前的代码:
var windowsDir = new DynamicFileSystem("dirWindows","Windows"); //Windows文件夹 var programDir = new DynamicFileSystem("dirProgram","Program Files"); //Program Files文件夹 //添加到C盘 fileSystem.add(windowsDir); fileSystem.add(programDir);
OK,到了这一步,一切都顺利的完成了。
如果你是一个爱问的同学,你想知道,如果我突然需要安装一个软件,该软件的安装目录必须为C:Program FilesMicrosoft,安装完成后还会在Microsoft目录下产生一个microsoft.exe的文件,又该怎么模拟呢?
照猫画虎:
第一步,创建ExeFile类 第二步,创建Microsoft目录 第三步,将其添加到C:/Program Files下 第四步,创建ExeFile类的实例microsoft.exe文件 第五步,将microsoft.exe添加到C:/Program Files/Microsoft目录下,完成
总结
如果应用得当,组合模式将是一种非常管用的设计模式。它把一批子对象组织为树形结构,只要一条命令就可以操作树上的所有对象,提高了代码的模块化程度,而且便于代码重构和对象的更换。 这种模式特别适合于动态的HTML用户界面,在它的帮助下,你可以在不知道用户界面的最终格局的情况下进行开发。对于我们每一个前端Javascript程序员来说,能掌握好组合模式,一定会大受益处滴。
本文参考《Javascript设计模式》【Ross Harmes,Dustin Diaz】
小生愚昧,文中如有阐述之不当,还请不要介怀