设计模式——组合模式
俗话说无规矩不成方圆。在现实生活中,无论什么团体都会有自己的一套规章制度来管理,就像军队一样,即使人数众多也能在集体活动时保持统一与协调。在程序设计中也是如此,有时候我们可能需要针对一个子元素相似的结构操作数据,这时候组合模式就派上用场了。
组合模式原理
组合模式将相似的对象组合成一个整体树形结构,并且提供一种遍历树形结构的方案。核心思想就是统一部分和整体。利用对象多态性给整体和部分设计相同的接口,刻意使用户忽略组合对象和子对象的不同。这是非常有用的,有点像高中分析受力的整体法,从全局来操控。
举个例子,比如我们现在有一堆形态不一的物体,需要从A搬到B。而这些物体呢有的只能用瓶子装着移动,有的只能抱着移动,分开处理肯定是比较麻烦的。那么我们给他们共同设计一个“推”的接口,将他们全部装入一辆小车。现在我们只用对小车使用推的指令,就可以同时调用这些物体的"推"的接口,方便的运到目的地了。组合模式的核心思想就是如此。
组合模式实例
下面我们就以文件夹操作的实例来具体分析组合模式。文件夹和文件之间的关系应该是非常适合用组合模式的了,因为对用户来说文件夹也是文件。这正是组合模式的要素之一——忽略整体和部分的区别。
let Folder = function(name) {
this.name = name;
this.files = [];
};
Folder.prototype.add = function(file) {
this.files.push(file);
}
Folder.prototype.scan = function() {
for (let file of this.files) {
file.scan();
}
}
let File = function(name) {
this.name = name;
}
File.prototype.add = function() {
throw new Error('这不是一个文件夹')
}
File.prototype.scan = function() {
console.log('开始扫描文件',this.name);
}
可以看到我们特意设计了相同的功能接口,这样当从树结构顶端进行遍历时,就可以忽略叶对象和组合对象间的差异。
进一步改进
通过上例我们实现了组合模式的遍历和增加功能,当然也必须有删除功能。从常理上来说,删除一个节点必然要将它的所有子节点一并删除,一般从它的父节点来切断整体和局部的联系。所以需要明确各个节点之间的父子关系,给所有节点添加相关属性。改进如下:
let Folder = function(name) {
this.name = name;
this.parent = null;
this.files = [];
};
Folder.prototype.add = function(file) {
file.parent = this;
this.files.push(file);
};
Folder.prototype.remove = function() {
if (!this.parent) { // 不是子节点不需要移除
return;
}
for (let file of this.parent.files.entries()) {
if (file[1] = this) {
files.splice(file[0], 1);
}
}
}
遵循接口相同的思路,file的改动基本也是一致的。
小结
总之在程序设计时,如果感觉可以考虑高中时的所学的整体法来操作,就可以手动统一接口来使用组合模式。当某需求中父子结构本身就有着天然相同的接口,也应该优先考虑组合模式。
-- EOF --