【再谈设计模式】组合模式~层次构建的多面手

一、引言

        在软件开发的世界里,我们经常面临着处理对象之间复杂关系的挑战。如何有效地表示对象的部分 - 整体层次结构,并且能够以一种统一的方式操作这些对象,是一个值得探讨的问题。组合模式(Composite Pattern)为我们提供了一种优雅的解决方案,它使得客户端可以像处理单个对象一样处理对象组合。这种模式在很多领域都有着广泛的应用,从图形界面的构建到文件系统的组织,都能看到它的身影。

二、定义与描述

        组合模式是一种结构型设计模式,它允许将对象组合成树形结构来表示“部分 - 整体”的层次关系。组合模式使得用户对单个对象和组合对象的使用具有一致性。在组合模式中,有两种基本类型的对象:叶节点(Leaf)和组合节点(Composite)。叶节点是没有子节点的对象,而组合节点可以包含叶节点和其他组合节点。

三、抽象背景

        在许多实际的软件系统中,我们需要处理具有层次结构的数据或对象。例如,在文件系统中,文件夹可以包含文件和其他文件夹;在图形用户界面中,一个容器组件(如面板)可以包含其他组件(如按钮、文本框等)。如果没有一种合适的设计模式,对这种层次结构的操作将会变得复杂和难以维护。例如,我们可能需要编写大量的条件判断语句来区分对象是单个的还是组合的,这会导致代码的复杂性增加,并且容易出错。

四、适用场景与现实问题解决

  • 层次结构的数据存储和操作
    • 例如,在一个企业组织结构管理系统中,公司由多个部门组成,部门又可以包含子部门和员工。使用组合模式,可以方便地对整个组织结构进行管理,如统计员工数量、计算部门预算等。

  • 图形界面组件管理
    • 在图形界面开发中,窗口、面板、按钮等组件构成了一个层次结构。组合模式允许我们以统一的方式处理这些组件,例如,对整个界面进行布局调整或者显示/隐藏操作。

五、组合模式的现实生活的例子

  • 文件系统
    • 文件夹可以看作是组合节点,文件则是叶节点。我们可以对文件夹进行操作,如删除文件夹(这会递归地删除文件夹中的所有文件和子文件夹),也可以对单个文件进行操作,如打开、重命名等。

  • 菜单结构
    • 在餐厅的菜单中,菜单可以包含子菜单和菜品。整个菜单是一个组合结构,我们可以对整个菜单进行显示、定价等操作,也可以对单个菜品进行操作,如调整价格、描述等。

六、初衷与问题解决

        初衷是为了简化对具有层次结构的对象的操作,使得客户端不需要区分对象是单个的还是组合的。通过将对象组织成树形结构,并定义统一的操作接口,解决了在处理层次结构时代码复杂、难以维护的问题。

七、代码示例

实现类图举例:

  • 首先定义了抽象类 Component,它有一个私有属性 name,一个构造函数和一个抽象方法 operation
  • 然后定义了 Leaf 类,它继承自 Component 类,有自己的构造函数和实现了 operation 方法。
  • 接着定义了 Composite 类,它也继承自 Component 类,有一个私有属性 children 用于存储子组件,有构造函数、添加和移除子组件的方法以及实现了 operation 方法。
  • 最后通过关系符号表示了 Leaf 和 Composite 与 Component 的继承关系。

使用时序图:

  • Client 作为参与者,首先创建了 root(组合对象)、多个叶节点(leaf1leaf2subLeaf1subLeaf2)和一个子组合节点(subComposite)。
  • 然后将子叶节点添加到子组合节点中,将叶节点和子组合节点添加到根组合节点 root 中。
  • 最后,Client 调用 root 的 operation 方法,按照组合模式的逻辑,这个操作会递归地调用其包含的所有子对象(叶节点和子组合节点)的 operation 方法。

Java

// 组件抽象类
abstract class Component {protected String name;public Component(String name) {this.name = name;}public abstract void operation();
}// 叶节点类
class Leaf extends Component {public Leaf(String name) {super(name);}@Overridepublic void operation() {System.out.println("叶节点 " + name + " 执行操作");}
}// 组合节点类
class Composite extends Component {private java.util.ArrayList<Component> children = new java.util.ArrayList<>();public Composite(String name) {super(name);}public void add(Component component) {children.add(component);}public void remove(Component component) {children.remove(component);}@Overridepublic void operation() {System.out.println("组合节点 " + name + " 执行操作");for (Component child : children) {child.operation();}}
}public class Main {public static void main(String[] args) {Composite root = new Composite("根节点");Leaf leaf1 = new Leaf("叶节点1");Leaf leaf2 = new Leaf("叶节点2");Composite subComposite = new Composite("子组合节点");Leaf subLeaf1 = new Leaf("子叶节点1");Leaf subLeaf2 = new Leaf("子叶节点2");subComposite.add(subLeaf1);subComposite.add(subLeaf2);root.add(leaf1);root.add(leaf2);root.add(subComposite);root.operation();}
}

C++

#include <iostream>
#include <vector>// 组件抽象类
class Component {
protected:std::string name;
public:Component(std::string name) : name(name) {}virtual void operation() = 0;
};// 叶节点类
class Leaf : public Component {
public:Leaf(std::string name) : Component(name) {}void operation() override {std::cout << "叶节点 " << name << " 执行操作" << std::endl;}
};// 组合节点类
class Composite : public Component {
private:std::vector<Component*> children;
public:Composite(std::string name) : Component(name) {}void add(Component* component) {children.push_back(component);}void remove(Component* component) {for (auto it = children.begin(); it!= children.end(); ++it) {if (*it == component) {children.erase(it);break;}}}void operation() override {std::cout << "组合节点 " << name << " 执行操作" << std::endl;for (auto child : children) {child->operation();}}
};int main() {Composite root("根节点");Leaf leaf1("叶节点1");Leaf leaf2("叶节点2");Composite subComposite("子组合节点");Leaf subLeaf1("子叶节点1");Leaf subLeaf2("子叶节点2");subComposite.add(&subLeaf1);subComposite.add(&subLeaf2);root.add(&leaf1);root.add(&leaf2);root.add(&subComposite);root.operation();return 0;
}

Python

# 组件抽象类
class Component:def __init__(self, name):self.name = namedef operation(self):pass# 叶节点类
class Leaf(Component):def operation(self):print(f"叶节点 {self.name} 执行操作")# 组合节点类
class Composite(Component):def __init__(self, name):super().__init__(name)self.children = []def add(self, component):self.children.append(component)def remove(self, component):self.children.remove(component)def operation(self):print(f"组合节点 {self.name} 执行操作")for child in self.children:child.operation()if __name__ == "__main__":root = Composite("根节点")leaf1 = Leaf("叶节点1")leaf2 = Leaf("叶节点2")subComposite = Composite("子组合节点")subLeaf1 = Leaf("子叶节点1")subLeaf2 = Leaf("子叶节点2")subComposite.add(subLeaf1)subComposite.add(subLeaf2)root.add(leaf1)root.add(leaf2)root.add(subComposite)root.operation()

Go

package mainimport ("fmt"
)// 组件接口
type Component interface {operation()
}// 叶节点结构体
type Leaf struct {name string
}func (l *Leaf) operation() {fmt.Printf("叶节点 %s 执行操作\n", l.name)
}// 组合节点结构体
type Composite struct {name     stringchildren []Component
}func (c *Composite) add(component Component) {c.children = append(c.children, component)
}func (c *Composite) remove(component Component) {for i, child := range c.children {if child == component {c.children = append(c.children[:i], c.children[i+1:]...)break}}
}func (c *Composite) operation() {fmt.Printf("组合节点 %s 执行操作\n", c.name)for _, child := range c.children {child.operation()}
}func main() {root := &Composite{"根节点", []Component{}}leaf1 := &Leaf{"叶节点1"}leaf2 := &Leaf{"叶节点2"}subComposite := &Composite{"子组合节点", []Component{}}subLeaf1 := &Leaf{"子叶节点1"}subLeaf2 := &Leaf{"子叶节点2"}subComposite.add(subLeaf1)subComposite.add(subLeaf2)root.add(leaf1)root.add(leaf2)root.add(subComposite)root.operation()
}

八、组合模式的优缺点

优点

  • 简化客户端代码
    • 客户端不需要区分操作的对象是单个的还是组合的,统一的接口使得操作更加简单。
  • 可扩展性好
    • 容易添加新的叶节点或组合节点类型,对现有代码的影响较小。
  • 层次结构清晰
    • 能够清晰地表示对象之间的层次关系,便于理解和维护。

缺点

  • 限制叶节点和组合节点的共性
    • 在定义抽象组件时,需要考虑叶节点和组合节点的共性操作,如果共性操作较少,可能会导致抽象组件接口的定义不够合理。
  • 可能导致设计过度复杂
    • 在一些简单的层次结构场景中,如果使用组合模式,可能会引入不必要的复杂性。

九、组合模式的升级版

  • 安全组合模式
    • 在普通的组合模式中,叶节点和组合节点都实现相同的接口,这可能会导致一些安全隐患,例如叶节点可能被误当作组合节点进行添加操作。安全组合模式通过将叶节点和组合节点的接口分开定义,增加了类型安全性。

  • 透明组合模式
    • 透明组合模式中,叶节点和组合节点具有完全相同的接口,这使得客户端可以完全透明地操作对象。但是这种模式可能会导致一些语义上的混淆,例如叶节点可能被赋予了一些对它无意义的操作(如添加子节点)。在实际应用中,可以根据具体需求选择合适的组合模式升级版本。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/489004.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

论文翻译 | ChunkRAG: Novel LLM-Chunk Filtering Method for RAG Systems

摘要 使用大型语言模型&#xff08;LLM&#xff09;的检索-增强生成&#xff08;RAG&#xff09;系统经常由于检索不相关或松散相关的信息而生成不准确的响应。现有的在文档级别操作的方法无法有效地过滤掉此类内容。我们提出了LLM驱动的块过滤&#xff0c;ChunkRAG&#xff0…

Redis配置文件中 supervised指令

什么是Supervised&#xff1f; supervised模式允许Redis被外部进程管理器监控。通过这个选项&#xff0c;Redis能够在崩溃后自动重启&#xff0c;确保服务的高可用性。常见的进程管理器包括systemd和upstart。 开启方法 vim修改&#xff1a; sudo vi /etc/redis/redis.conf…

Android四大组件——Activity(二)

一、Activity之间传递消息 在&#xff08;一&#xff09;中&#xff0c;我们把数据作为独立的键值对进行传递&#xff0c;那么现在把多条数据打包成一个对象进行传递&#xff1a; 1.假设有一个User类的对象&#xff0c;我们先使用putExtra进行传递 activity_demo06.xml <…

SpringBoot基于Redis+WebSocket 实现账号单设备登录.

引言 在现代应用中&#xff0c;一个账号在多个设备上的同时登录可能带来安全隐患。为了解决这个问题&#xff0c;许多应用实现了单设备登录&#xff0c;确保同一个用户只能在一个设备上登录。当用户在新的设备上登录时&#xff0c;旧设备会被强制下线。 本文将介绍如何使用 Spr…

【架构】从 Socket 的角度认识非阻塞模型

文章目录 前言1. 阻塞模型2. 非阻塞模型2.1 Reactor 模型优势2.2 Reactor 模型劣势 后记 前言 近期看了很多中间件的文章&#xff0c;RocketMQ&#xff0c;Dubbo 这些中间件内部的rpc通信都用的是非阻塞的模型。(Netty)&#xff0c;这里从 Socket 的角度总结一下。 1. 阻塞模…

location和重定向、代理

location匹配的规则和优先级 在nginx当中&#xff0c;匹配的对象一般是URI来匹配 http://192.168.233.62/usr/local/nginx/html/index.html 182.168.233.61/ location匹配的分类&#xff1a; 多个location一旦匹配其中之一&#xff0c;不在匹配其他location 1、精确匹配 …

ragflow连ollama时出现的Bug

ragflow和ollama连接后&#xff0c;已经添加了两个模型但是ragflow仍然一直warn&#xff1a;Please add both embedding model and LLM in Settings &#xff1e; Model providers firstly.这里可能是我一开始拉取的镜像容器太小&#xff0c;容不下当前添加的模型&#xff0c;导…

软件测试面试问答

文章目录 什么是软件&#xff1f;软件测试工程师的工作内容什么是软件测试&#xff1f;软件开发生命周期软件开发的几个阶段软件bug的五个要素Bug的十大要素:软件测试的分类软件测试方法分类单元测试设计测试用例的主要方法什么是测试用例测试用例几大要素你的测试职业发展是什…

python学习笔记—7—变量拼接

1. 字符串的拼接 print(var_1 var_2) print("supercarry" "doinb") name "doinb" sex "man" score "100" print("sex:" sex " name:" name " score:" score) 注意&#xff1a; …

Win10环境vscode+latex+中文快速配置

安装vscodelatex workshop 配置&#xff1a; {"liveServer.settings.donotVerifyTags": true,"liveServer.settings.donotShowInfoMsg": true,"explorer.confirmDelete": false,"files.autoSave": "afterDelay","exp…

AI生成不了复杂前端页面?也许有解决方案了

在2024年&#xff0c;编程成为了人工智能领域最热门的赛道。AI编程技术正以惊人的速度进步&#xff0c;但在生成前端页面方面&#xff0c;AI的能力还是饱受质疑。自从ScriptEcho平台上线以来&#xff0c;我们收到了不少用户的反馈&#xff0c;他们表示&#xff1a;“生成的页面…

【热力学与工程流体力学】流体静力学实验,雷诺实验,沿程阻力实验,丘里流量计流量系数测定,局部阻力系数的测定,稳态平板法测定材料的导热系数λ

关注作者了解更多 我的其他CSDN专栏 过程控制系统 工程测试技术 虚拟仪器技术 可编程控制器 工业现场总线 数字图像处理 智能控制 传感器技术 嵌入式系统 复变函数与积分变换 单片机原理 线性代数 大学物理 热工与工程流体力学 数字信号处理 光电融合集成电路…

360极速浏览器不支持看PDF

360安全浏览器采用的是基于IE内核和Chrome内核的双核浏览器。360极速浏览器是源自Chromium开源项目的浏览器&#xff0c;不但完美融合了IE内核引擎&#xff0c;而且实现了双核引擎的无缝切换。因此在速度上&#xff0c;360极速浏览器的极速体验感更佳。 展示自己的时候要在有优…

【深度学习】深刻理解ViT

ViT&#xff08;Vision Transformer&#xff09;是谷歌研究团队于2020年提出的一种新型图像识别模型&#xff0c;首次将Transformer架构成功应用于计算机视觉任务中。Transformer最初应用于自然语言处理&#xff08;如BERT和GPT&#xff09;&#xff0c;而ViT展示了其在视觉任务…

idea 配置 git .gitignore文件配置

.gitignore 内容 .idea/ *.iml target/ *.class *.log .iml在idea项目里面创建一个.gitignore名字的文件&#xff0c;然后把这个文件提交到git上。我一般是放到.idea同级目录。 我遇到了几种情况这个文件配置了但是不生效的情况 第一种 Git的缓存可能会导致配置不生效。尝试…

Scala的隐式转换

&#xff08;一&#xff09;隐式转换&#xff1a; 编译器 偷偷地&#xff0c;自动地帮我们把一种数据类型转换为另一种类型。例如&#xff1a;int --> double 它有失败的时候(double-->int),有成功的时候。 当它转换失败的时候&#xff0c;我们提供一个工具&#xff0c;让…

Windows安装WSL子系统及docker,以及WSL和docker配置、使用及问题解决

在Windows操作系统中,Ubuntu子系统(也称为Windows Subsystem for Linux, WSL)为开发者提供了一个在Windows环境下运行Linux环境的平台。然而,有时用户在按照Ubuntu子系统或者使用WSL时,可能会遇到各种问题,下面总结一下解决方式。 想要在Windows上安装Docker(实际上是基…

F12抓包01:启动、面板功能介绍、语言设置、前端样式调试

浏览器检查工具通常用来作为浏览器web服务测试过程中&#xff0c;辅助测试、排查问题、定位缺陷的工具。 本文以mac系统下&#xff0c;当前比较常用的Chrome浏览器为例&#xff0c;讲解“检查”工具的常用功能操作方法。 一、打开方式 **1、****鼠标操作&#xff1a;**浏览器…

仿iOS日历、飞书日历、Google日历的日模式

仿iOS日历、飞书日历、Google日历的日模式&#xff0c;24H内事件可自由上下拖动、自由拉伸。 以下是效果图&#xff1a; 具体实现比较简单&#xff0c;代码如下&#xff1a; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color;…

特征交叉-CAN学习笔记代码解读

一 核心模块coaction 对于每个特征对(feature_pairs)weight, bias 来自于P_inductionP_fead是MLP的input 举个例子&#xff1a;如果是用户ID和产品ID的co-action&#xff0c;且产品ID是做induction&#xff0c;用户ID是做feed。 step1 用户ID/产品ID都先形成一个向量&#xf…