前言
这篇文章的初衷是由于最近在工作当中,碰到了很久之前的一个同事写的代码,功能大体是完成一个jstree的增删改等功能,但在看代码的过程中,感觉代码结构比较混乱,为此在基于组合模式的设计理念上对代码进行一次整理。
组合模式
在JS当中,组合模式可以简单理解为由对象组成的树形结构,如图:
A为树的跟节点,B为A的子节点,同时B又是DE的父节点。但是在组合模式中,AB并不是父子关系,而是用相同接口的对象,来进行统一操作,是一种HAS-A(聚合)的关系,而不是IS-A。
IS-A代表的是类之间的继承关系,比如PC机是计算机,工作站也是计算机。
PC机和工作站是两种不同类型的计算机,但都继承了计算机的共同特性。
HAS-A代表的是对象和它的成员的从属关系。同一种类的对象,通过它们的属性的不同值来区别。
比如一台PC机的操作系统是Windows,另一台PC机的操作系统是Linux。
操作系统是PC机的一个成员变量,根据这一成员变量的不同值,可以区分不同的PC机对象。
而在组合模式当中,因为在这棵树中的每一个节点都会执行相同的操作,而每一个节点看作是一个对象,那么就可以认为是一组具有相同属性方法的对象的集合。talk is cheap,show you the code。
code review
首先有这样一个tree,可以在选中某一节点之后,对该节点进行添加子节点,删除当前节点等操作。之前项目代码回顾:
可以看出之前代码存在比较明显的缺点是,各个方法使用构建函数创建并调用,并且功能逻辑比较分散,导致我在重新阅读代码时,需要不断地去找代码,效率很低。对于读代码的人来说,这是一段可读性较差的代码。而站在写代码的人角度上看,这样的代码也不利于维护。
而业务本身的需求只是让选中的节点,具有增删等功能,换句话说,不论他处在那一个层级,都执行相同的操作。这时,就比较适合使用组合模式去处理。
class node {
constructor(name) {
this.name = name;
this.parent = null;
this.nodes = [];
}
add(cNode) {
cNode.parent = this;
this.nodes.push[cNode]
}
del() {
if (!this.parent)return false;
let nodes = this.parent.nodes;
for (let [key, val] of nodes) {
val == this ? nodes.splice(key, 1) : false;
}
}
execute(){
for (let [key, val] of this.nodes) {
this.nodes[key].execute();
}
}
}
class cNode {
constructor(name) {
this.name = name;
this.parent = null;
}
add(cNode) {
throw new Error('已达到节点下限');
}
del() {
if (!this.parent)return false;
let nodes = this.parent.nodes;
for (let [key, val] of nodes) {
val == this ? nodes.splice(key, 1) : false;
}
}
execute(){
// do something
}
}
(代码纯手敲,未经任何测试,纯展示结构)
这样写的好处是,将视图中的父子节点中的逻辑整理组织起来,可读性上要进步一些,而且下次在增加一个类似编辑的功能时,也不用像之前一样随便插到代码里面。
总结
组合模式让代码的结构更加清晰,更加方便于后来的人去阅读,维护。这种模式只是结构上的改观,并没有能让代码的性能提高。不过,在实际的业务开发中,个人认为,一块业务很难完全脱离团队,不可避免要被阅读,因此可读性是放在第一位的,其次是代码的健壮性,可扩展性,如果只是基于业务的话,而不是专门用来处理数据,或者是公用库的话性能是最后考虑的。