本篇文章参考书籍《JavaScript设计模式》–张容铭
前言
来了各位,本节整个硬活,组合模式,这个模式在应对复杂需要的时候有超高的灵活性,举个例子,乐高大家都玩过吧,各种各样的模型都能拼。乐高的拼装有个特点,一块积木上面是一个一个的小圆珠体,下面是可以衔接圆柱体的卡槽。
通过不同的组合我们可以拼出很多我们像要的东西,只有想不到的需求,没有实现不了的方案。
注意事项
使用组合模式的时候,需要有两点注意:
1. 所有接口要统一,所有类都要继承于一个虚拟父类(这一点保证所有积木都可以拼接在一起)
2. 叶子节点的子类不允许再进行组合(类似下图中红色区域,该子类上面不需要再组合其他东西了,所有不需要再有圆柱体了)
组合模式
1.同一父类
首先要满足所有子类都继承于一个虚拟父类。下面我们创建一个关于新闻的组合模式。
var News = function() {
//子组件容器
this.children = [];
//当前组件元素
this.element = null;
}
News.prototype = {
init: function() {
throw new Error('重写方法');
},
add: function() {
throw new Error('重写方法');
},
getElement: function() {
throw new Error('重写方法');
}
}
2.组合需要有容器
所有子类是放在这个容器当中的。
//容器构造类函数
var Container = function(id, parent) {
//构造函数继承父类
News.call(this);
//模块id
this.id = id;
//模块父容器
this.init();
}
//寄生组合继承
function inheritPrototype(subType, superType){
let prototype = Object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
inheritPrototype(Container, News);
//构建方法
Container.prototype.init = function() {
this.element = document.createELement('ul');
this.element.id = this.id;
this.element.className = 'new-container';
}
//添加子元素方法
Container.prorotype.add = function(child) {
//在子元素容器中插入子元素
this.children.push(child);
//插入当前组件元素树中
this.element.appendChild(child.getElement());
return this;
}
//获取当前元素方法
Container.prototype.getElement = function() {
return this.element;
}
//显示方法
Container.prototype.show = function() {
this.parent.appendChild(this.element);
}
下一层级构造方式与上面类似。
//下一层级成员
var Item = function(classname) {
News.call(this);
this.classname = classname || '';
this.init();
}
inheritPrototype(Item, News);
Item.prototype.init = function(child) {
this.element = document.createElement('li');
this.element.calssName = this.classname;
}
Item.prototype.add = function(child) {
//在子元素容器中插入子元素
this.children.push(child);
//插入当前组件元素树中
this.element.appendChild(child.getElement());
return this;
}
Item.prototype.getElement = function() {
return this.element;
}
var NewsGroup = function(calssname) {
News.call(this);
this.classname = classname || '';
this.init();
}
inheritPrototype(NewsGroup, News);
NewsGroup.prototype.init = function() {
this.element = document.createElement('div');
this.element.calssName = this.classname;
}
NewsGroup.prototype.add = function(child) {
//在子元素容器中插入子元素
this.children.push(child);
//插入当前组件元素树中
this.element.appendChild(child.getElement());
return this;
}
NewsGroup.prototype.getElement = function() {
return this.element;
}
3.创建子类成员
容器造好之后,我们要创建每一个叶子节点类
var ImageNews = function(url, href, calssname) {
News.call(this);
this.url = url || '';
this.href = href || '#';
this.calssname = calssname || 'normal';
this.init();
}
inheritPrototype(ImageNews, News);
ImageNews.prototype.init = function() {
this.element = document.createElement('a');
var Img = new Image();
img.src = this.url;
this.element.appendChild(img);
this.element.classname = 'image-news ' + this.calssname;
this.element.href = this.href;
}
ImageNews.prototype.add = function() {}
ImageNews.prototype.getElement = function() {
return this.element;
}
var IconNews = function(text, href, type) {
News.call(this);
this.text = text || '';
this.href = href || '#';
this.type = type || 'video';
this.init();
}
inheritPrototype(IconNews, News);
IconNews.prototype.init = function() {
this.element = document.createElement('a');
this.element.innerHTML = this.text;
this.element.href = this.href;
this.element.classname = 'icon ' + this.type;
}
ImageNews.prototype.add = function() {}
ImageNews.prototype.getElement = function() {
return this.element;
}
var EasyNews = function(text, href) {
News.call(this);
this.text = text || '';
this.href = href || '#';
this.init();
}
inheritPrototype(EasyNews, News);
EasyNews.prototype.init = function() {
this.element = document.createElement('a');
this.element.innerHTML = this.text;
this.element.href = this.href;
this.element.classname = 'text ';
}
EasyNews.prototype.add = function() {}
EasyNews.prototype.getElement = function() {
return this.element;
}
var TypeNews = function(text, href, type, pos) {
News.call(this);
this.text = text || '';
this.href = href || '#';
this.type = type || '';
this.pos = pos || 'left';
this.init();
}
inheritPrototype(TypeNews, News);
TypeNews.prototype.init = function() {
this.element = document.createElement('a');
if(this.pos = 'left') {
this.element.innerHTML = '[' + this.type + ']' + this.text;
} else {
this.element.innerHTML = this.text + '[' + this.type + ']';
}
this.element.href = this.href;
this.element.classname = 'text ';
}
TypeNews.prototype.add = function() {}
TypeNews.prototype.getElement = function() {
return this.element;
}
4.创建一个完整的类
新闻类模块创建完成了,使用时需要使用 add 方法,像一棵树一样,一层一层的创建。
var newsl = new Container('news', document.body);
newsl.add(
new Item('normal').add(
new IconNews('1024程序员节,你coding了嘛', '#', 'video')
)
).add(
new Item('normal').add(
new IconNews('双十一,你冲了吗', '#', 'live')
)
).add(
new Item('normal').add(
new NewsGroup('has-img').add(
new ImageNews('img/1.jpg', '#', 'small')
).add(
new EasyNews('湖人总冠军', '#')
)
)
).add(
new Item('normal').add(
new TypeNews('詹姆斯球鞋冠军配色', '#', 'NBA', 'left')
)
).add(
new Item('normal').add(
new TypeNews('易建联生日快乐', '#', 'CBA', 'Right')
)
).show();
创建的每一条新闻都是一个独立的个体,互不影响,避免相互间的耦合,同时也增强了组合后模块的复杂性,以后不论有什么样的需求,只要做出相应的组合就可以轻松完成。
5.表单当中更灵活
平常我们使用组合模式最多的地方就是表单提交,这一部分因为每一项都要求独立,且不同项目的表单都有点差异,所以很适合用组合模式。
做法和上面的一样,先创建一个基类 Base ,然后创建三个组合类 FromItem , FieldsetItem , Group , 还有成员类 InputItem , LabelItem , SpanItem , TextareaItem 。
var form = new FromItem('FromItem', document.body);
form.add(
new FieldsetItem('account', '账号').add(
new Group().add(
new LabelItem('user_name', '用户名');
).add(
newInputItem('user_name');
).add(
new SpanItem('4到6位数字或字母');
)
).add(
new Group().add(
new LabelItem('user_password', '密 码:')
).add(
new InputItem('user_password')
).add(
new SpanItem('6到12位数字或密码')
)
)
).add(
//...
).show();
组合模式刚上手有点乱,不过核心思想还是比较清晰明确的,把需求拆分成一个一个子类,让所有子类继承一个虚拟父类就可以了。之前问过一位高人,如何才能写好代码,前辈指点:“ 惟手熟尔 ”。设计模式初学的时候,一定要多用,用的多,才能记得住。