JavaScript组合模式详解:从类继承到统一接口
本文深入解析JavaScript中的组合模式,通过公司组织架构示例,对比传统类继承与组合模式的差异。重点展示如何利用统一接口处理叶子节点与容器节点,实现树形结构的透明操作,并分析其优缺点及适用场景。
本文通过具体实例解析JavaScript面向对象程序设计中的组合模式,旨在为读者提供可参考的实现方案。
组合模式概述
概述
关于组合模式的定义:组合模式(Composite Pattern)有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。来自百度百科:http://baike.baidu.com/view/3591789.htm
自面向对象系列第五篇之后,内容重心逐渐从JavaScript语言特性转向设计模式概念。只要具备JavaScript面向对象的基础知识并理解设计模式的核心含义,代码实现本身并不复杂。
组合模式的核心在于将一系列相似或相近的对象组合到一个大型对象中,由该大型对象提供常用接口以操作这些子对象,从而实现代码重用并简化对外操作。例如,在Form表单中,若忽略页面设计细节,主要元素为Input。由于所有Input均具备name和value属性,可将这些Input元素作为Form对象的成员进行组合。Form对象对外提供接口,即可实现如设置Input值、添加或删除Input等简单操作。
正文
组合模式,又称部分整体模式,用于将一组相似对象视为单一对象。该模式依据树形结构组合对象,用以表示部分与整体之间的层次关系。
其定义为:组合多个对象形成树形结构,以表示具有整体-部分关系的层次机构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,因此也被称为整体-部分模式。这是一种对象结构型模式。
公司架构示例与问题
场景示例:假设我们需要打印公司的人员架构,且管理岗与开发岗的区别仅在于是否存在下级员工。下面我们将通过代码实现这一场景。
示例代码展示:
var LEADER = function(name,dept){
this._name = name || ''; //姓名
this._dept = dept || ''; //职位
this._subordinates = []; //下属
this.add = function(employee){
this._subordinates.push(employee);
}
this.remove = function(employee){
this._subordinates.splice(this._subordinates.indexOf(employee),1);
}
this.getSubordinates = function(){
return this._subordinates;
}
this.toString = function(){
console.log('姓名:'+this._name+',职位:'+this._dept)
}
}
var JAVARD = function(name,dept){
this._name = name || ''; //姓名
this._dept = dept || ''; //职位
this.toString = function(){
console.log('姓名:'+this._name+',职位:'+this._dept)
}
}
var FERD = function(name,dept){
this._name = name || ''; //姓名
this._dept = dept || ''; //职位
this.toString = function(){
console.log('姓名:'+this._name+',职位:'+this._dept)
}
}
function addData(){
var CEO = new LEADER('spancer','CEO');
var CTO = new LEADER('zijian','CTO');
var MANAGER = new LEADER('jiang','LEADER');
var JAVA_LEADER = new LEADER('fei','JAVA_LEADER');
var FE_LEADER = new LEADER('risker','FE_LEADER');
var wh = new FERD('wanghui','FE');
var si = new FERD('si','FE');
var amy = new FERD('amy','FE');
var wei = new JAVARD('wei','JAVA');
var guo = new JAVARD('guo','JAVA');
var yuan = new JAVARD('yuan','JAVA');
CEO.add(CTO);
CTO.add(MANAGER);
MANAGER.add(JAVA_LEADER);
MANAGER.add(FE_LEADER);
FE_LEADER.add(wh);
FE_LEADER.add(si);
FE_LEADER.add(amy);
JAVA_LEADER.add(wei);
JAVA_LEADER.add(guo);
JAVA_LEADER.add(yuan);
return CEO;
}
var eachEmployee = function(employee){
for(var employ of employee.getSubordinates()){
employ.toString();
if(employ.getSubordinates && employ.getSubordinates().length > 0){
eachEmployee(employ);
}
}
}
var CEO = addData();
CEO.toString();
eachEmployee(CEO);
// 姓名:spancer,职位:CEO
// 姓名:zijian,职位:CTO
// 姓名:jiang,职位:LEADER
// 姓名:fei,职位:JAVA_LEADER
// 姓名:wei,职位:JAVA
// 姓名:guo,职位:JAVA
// 姓名:yuan,职位:JAVA
// 姓名:risker,职位:FE_LEADER
// 姓名:wanghui,职位:FE
// 姓名:si,职位:FE
// 姓名:amy,职位:FE
上述简单Demo用于遍历输出公司组织架构。由于研发(rd)与领导(leader)职能不同,我们将技术岗与管理岗分为两类。然而,这种设计存在诸多问题:
* 可扩展性较差。当出现新职位时,是新增类目还是归入现有类目均成为难题。 * 当某一行为发生变化时,需逐个修改Leader类和Rd类,这不符合开闭原则。
接下来,我们使用组合模式重新实现该功能:
组合模式实现与总结
var Employee = function(name, dept){
this._name = name || ''; //姓名
this._dept = dept || ''; //职位
this._subordinates = []; //下属
this.add = function(employee){
this._subordinates.push(employee);
}
this.remove = function(employee){
this._subordinates.splice(this._subordinates.indexOf(employee),1);
}
this.getSubordinates = function(){
return this._subordinates;
}
this.toString = function(){
console.log('姓名:'+this._name+',职位:'+this._dept)
}
}
function addData(){
var CEO = new Employee('spancer','CEO');
var CTO = new Employee('zijian','CTO');
var LEADER = new Employee('jiang','LEADER');
var JAVA_LEADER = new Employee('fei','JAVA_LEADER');
var FE_LEADER = new Employee('risker','FE_LEADER');
var wh = new Employee('wanghui','FE');
var si = new Employee('si','FE');
var amy = new Employee('amy','FE');
var wei = new Employee('wei','JAVA');
var guo = new Employee('guo','JAVA');
var yuan = new Employee('yuan','JAVA');
CEO.add(CTO);
CTO.add(LEADER);
LEADER.add(JAVA_LEADER);
LEADER.add(FE_LEADER);
FE_LEADER.add(wh);
FE_LEADER.add(si);
FE_LEADER.add(amy);
JAVA_LEADER.add(wei);
JAVA_LEADER.add(guo);
JAVA_LEADER.add(yuan);
return CEO;
}
var eachEmployee = function(employee){
for(var employ of employee.getSubordinates()){
employ.toString();
if(employ.getSubordinates().length > 0){
eachEmployee(employ);
}
}
}
var CEO = addData();
CEO.toString();
eachEmployee(CEO);
// 姓名:spancer,职位:CEO
// 姓名:zijian,职位:CTO
// 姓名:jiang,职位:LEADER
// 姓名:fei,职位:JAVA_LEADER
// 姓名:wei,职位:JAVA
// 姓名:guo,职位:JAVA
// 姓名:yuan,职位:JAVA
// 姓名:risker,职位:FE_LEADER
// 姓名:wanghui,职位:FE
// 姓名:si,职位:FE
// 姓名:amy,职位:FE
对比两段代码,差异在于我们使用一个Employee类替换了原有的Leader和Rd类。这正是组合模式的关键:定义一个抽象类,它既能代表Leader也能代表Rd。在添加和打印操作时,基于Employee类进行处理,无需知晓具体角色,从而实现对所有对象的统一处理。
组合模式总结:
优点: * 能够清晰定义具有层次关系的复杂对象,使客户端在开发过程中忽略层次差异。 * 全局修改时,只需修改一处位置。
缺点: * 无法对生成结果进行严格限制。例如,在第一个例子中,所有Rd都没有下级员工属性及对应方法,而组合模式下可能无法体现这一约束。因此使用时需注意这些限制条件。
适用场景: * 在面向对象语言开发系统中,需要处理树形结构时。 * 在具有整体和部分的结构中,希望忽略二者差异,使客户端能够一致对待时。
感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。
怎么在本地运行JavaScript脚本:Node.js下载安装与环境配置教程
2026-11-05 09:02:33
JavaScript开发环境怎么搭建:Node.js安装与VS Code配置指南
2026-11-03 23:36:59
JavaScript零基础入门:环境搭建与第一个Hello World
2026-11-03 23:34:09
NW.js 是什么:基于 Chromium 和 Node.js 的桌面应用框架
2026-11-03 15:14:00
nvm、n 和官方安装包:哪种 Node.js 升级方式更适合你
2026-11-03 14:54:28
