组合设计模式
定义
- 一种结构型设计模式,它允许我们将对象组合成树形结构以表示"部分-整体"的层次结构。
- 在这种模式中,单个对象和组合对象都被当作相同类型的对象对待,从而使客户端可以统一地处理它们。
参与者
组件(Component):定义组合对象的通用接口,声明了可以管理子组件的方法,例如增加、删除、获取子组件等。
叶子节点(Leaf):表示组合中的叶子节点对象,叶子节点没有子节点。它实现了组件接口的方法,但是对于管理子组件的方法没有实际操作。
容器节点(Composite):表示组合中的容器节点对象,容器节点包含子节点。它实现了组件接口的方法,并且对于管理子组件的方法进行了具体实现。
作用
-
使用组合设计模式可以方便地处理复杂对象的层次结构,
-
同时也能够使客户端代码更加简洁,
-
不需要关心处理的是单个对象还是组合对象,常用于树形结构的建模,例如文件系统、图形界面等场景。
示例
interface Component {
add(component: Component): void | never;
remove(component: Component): void | never;
getChild(index: number): Component | null | never;
operation(): void;
}
class Leaf implements Component {
operation(): void {
console.log("执行叶子节点操作");
}
add(component: Component): never {
throw new Error(`叶子节点没有子节点,不需要实现该方法`);
}
remove(component: Component): never {
throw new Error(`叶子节点没有子节点,不需要实现该方法`);
}
getChild(index: number): never {
throw new Error(`叶子节点没有子节点,返回null`);
}
}
class Composite implements Component {
private children: Component[] = [];
operation(): void {
console.log("执行容器节点操作");
for (const child of this.children) {
child.operation();
}
}
add(component: Component): void {
this.children.push(component);
}
remove(component: Component): void {
const index = this.children.indexOf(component);
if (index !== -1) {
this.children.splice(index, 1);
}
}
getChild(index: number): Component | null {
if (index >= 0 && index < this.children.length) {
return this.children[index];
} else {
return null;
}
}
}
const root = new Composite();
const leaf1 = new Leaf();
const leaf2 = new Leaf();
const composite = new Composite();
const leaf3 = new Leaf();
root.add(leaf1);
root.add(composite);
composite.add(leaf2);
composite.add(leaf3);
// 执行根节点操作,会递归执行子节点的操作
root.operation();
有趣的例子
interface Role {
getName(): string;
getAttributes(): string[];
useSkill(): void;
}
class Warrior implements Role {
getName(): string {
return "战士";
}
getAttributes(): string[] {
return ["力量", "耐力"];
}
useSkill(): void {
console.log("战士释放技能:猛击");
}
}
class Mage implements Role {
getName(): string {
return "法师";
}
getAttributes(): string[] {
return ["智力", "魔法"];
}
useSkill(): void {
console.log("法师释放技能:火球术");
}
}
class Thief implements Role {
getName(): string {
return "盗贼";
}
getAttributes(): string[] {
return ["敏捷", "隐身"];
}
useSkill(): void {
console.log("盗贼释放技能:潜行");
}
}
class Party implements Role {
private members: Role[] = [];
getName(): string {
return "队伍";
}
getAttributes(): string[] {
const attributes: string[] = [];
for (const member of this.members) {
attributes.push(...member.getAttributes());
}
return attributes;
}
useSkill(): void {
console.log("队伍释放技能:全体攻击");
for (const member of this.members) {
member.useSkill();
}
}
addMember(member: Role): void {
this.members.push(member);
}
removeMember(member: Role): void {
const index = this.members.indexOf(member);
if (index !== -1) {
this.members.splice(index, 1);
}
}
}
const warrior = new Warrior();
const mage = new Mage();
const thief = new Thief();
const party = new Party();
party.addMember(warrior);
party.addMember(mage);
party.addMember(thief);
console.log(party.getName()); // 输出:队伍
console.log(party.getAttributes()); // 输出:["力量", "耐力", "智力", "魔法", "敏捷", "隐身"]
warrior.useSkill(); // 输出:战士释放技能:猛击
mage.useSkill(); // 输出:法师释放技能:火球术
thief.useSkill(); // 输出:盗贼释放技能:潜行
party.useSkill(); // 输出:队伍释放技能:全体攻击
// 输出:
// 战士释放技能:猛击
// 法师释放技能:火球术
// 盗贼释放技能:潜行
应用场景
UI 组件库:UI 组件库通常包含各种复杂的组件,如按钮、表单、卡片等。这些组件可以被看作是一个组合结构,每个组件可以包含其他组件或嵌套组件。使用组合设计模式,我们可以定义一个通用的组件接口,并让每个组件实现该接口。这样,在使用组件库时,用户可以方便地处理单个组件和组件的组合。
导航菜单:网站或应用程序中的导航菜单通常是一个树形结构,包含多个菜单项和子菜单。通过使用组合设计模式,我们可以将每个菜单项和子菜单都视为组件,并统一对待它们。这样,在渲染导航菜单时,我们可以递归地遍历菜单组件,根据其类型进行相应的渲染和交互处理。
数据可视化图表:数据可视化图表通常由多个元素组成,如坐标轴、图例、数据点等。通过使用组合设计模式,我们可以将每个图表元素都视为组件,并按照一定的层次结构组织起来。这样,在绘制图表时,我们可以递归地遍历图表组件,并根据其类型进行相应的绘制和交互操作。
嵌套路由:在前端框架中,如 React、Vue 等,通常会使用嵌套路由来实现页面的层级结构。每个路由可以包含其他子路由或页面组件。使用组合设计模式,我们可以将每个路由都视为一个组件,并统一对待它们。这样,在定义和管理路由时,我们可以递归地遍历路由组件,根据其类型进行相应的路由配置和导航处理。
快速记忆
如何快速记住这个设计模式呢?实际上组合设计模式的本质就是【模块化】,在生活中最常见的例子就是,快餐店的套餐了,套餐中的薯条,汉堡,炸鸡腿都可以看成是继承自【食物类】的扩展类,而豪华套餐又可以包括A套餐和B套餐等,而吃掉套餐本质上是吃掉套餐中的薯条、炸鸡、汉堡等每一个元素。