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

news2024/12/13 16:47:00

一、引言

        在软件开发的世界里,我们经常面临着处理对象之间复杂关系的挑战。如何有效地表示对象的部分 - 整体层次结构,并且能够以一种统一的方式操作这些对象,是一个值得探讨的问题。组合模式(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);
    }

    @Override
    public 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);
    }

    @Override
    public 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 = name

    def 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 main

import (
    "fmt"
)

// 组件接口
type Component interface {
    operation()
}

// 叶节点结构体
type Leaf struct {
    name string
}

func (l *Leaf) operation() {
    fmt.Printf("叶节点 %s 执行操作\n", l.name)
}

// 组合节点结构体
type Composite struct {
    name     string
    children []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.coloradmin.cn/o/2258882.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于Git分支合并,跨仓库合并方式

关于Git合并代码的方式说明 文章目录 关于Git合并代码的方式说明前情提要开始合并方式一&#xff1a;git merge方式二&#xff1a;git cherry-pick方式三&#xff1a;git checkout Git跨仓库合并的准备事项前提拉取源仓库代码 前情提要 同仓库不同分支代码的合并可直接往下看文…

Android Freezer

Freezer原理 Android按照优先级将一般的APP从高到低分为: 前台进程 --> 可感知进程–> 服务进程 --> Cached进程。 Freezer通过冻住cached进程,来迫使这些进程让出CPU&#xff0c;以达到优化系统资源使用的目的。 Cached进程是怎么判定的呢&#xff1f; 由于andro…

websocker的java集成过程

第一步&#xff1a;引入依赖包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dependency> 第二步设置配置类&#xff1a; // 需要注入Bean的话必须声明为配置类 Co…

设计模式:24、访问者模式

目录 0、定义 1、访问者模式的五种角色 2、访问者模式的UML类图 3、示例代码 0、定义 表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各个元素的类的前提下&#xff0c;定义作用于这些元素的新操作。 1、访问者模式的五种角色 抽象元素&#xff08;Element…

umi实现动态获取菜单权限

文章目录 前景登录组件编写登录逻辑菜单的时机动态路由页面刷新手动修改地址 前景 不同用户拥有不同的菜单权限&#xff0c;现在我们实现登录动态获取权限菜单。 登录组件编写 //当我们需要使用dva的dispatch函数时&#xff0c;除了通过connect函数包裹组件还可以使用这种方…

Color-Light-Control-and-Four-Way-Responder based on STM32F103C8T6

Light Control and Responder 若要实现同样效果请看源码: gitee.com/apollo_666/Color-Light-Control-and-Four-Way-Responder # Abstract The design project for a decorative lighting controller enhanced our practical skills and engineering capabilities. During our…

数据库中的运算符

1.算术运算符 算术运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达式&#xff0c;对数值或表达式进行加&#xff08;&#xff09;、减&#xff08;-&#xff09;、乘&#xff08;*&#xff09;、除&#xff08;/&#xff09;和取模&#xff08;%&…

python爬虫--小白篇【爬取B站视频】

目录 一、任务分析 二、网页分析 三、任务实现 一、任务分析 将B站视频爬取并保存到本地&#xff0c;经过分析可知可以分为四个步骤&#xff0c;分别是&#xff1a; 爬取视频页的网页源代码&#xff1b;提取视频和音频的播放地址&#xff1b;下载并保存视频和音频&#x…

【计算机网络】实验18:动态主机配置协议DHCP的作用

实验18 动态主机配置协议DHCP的作用 一、实验目的 验证动态主机协议DHCP的作用 二、实验环境 Cisco Packet Tracer模拟器 三、实验过程 1.构建网络拓扑&#xff0c;不给局域网中的各主机手动配置IP地址、子网掩码、默认网关、DNS服务器等信息&#xff0c;而是开启动态主机…

MFC案例:基于对话框的简易阅读器

一、功能目标&#xff1a; 1.阅读txt文件 2.阅读时可以调整字体及字的大小 3.打开曾经阅读过的文件时&#xff0c;能够自动从上次阅读结束的位置开始显示&#xff0c;也就是能够保存和再次使用阅读信息。 4.对于利用剪贴板粘贴来的文字能够存储成txt文件保存。 5.显示…

【开源】基于SpringBoot框架的个性化的旅游网站 (计算机毕业设计)+万字毕业论文 T025

系统合集跳转 源码获取链接 一、系统环境 运行环境: 最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 IDE环境&#xff1a; Eclipse,Myeclipse,IDEA或者Spring Tool Suite都可以 tomcat环境&#xff1a; Tomcat 7.x,8.x,9.x版本均可 操作系统…

谷粒商城—分布式基础

1. 整体介绍 1)安装vagrant 2)安装Centos7 $ vagrant init centos/7 A `Vagrantfile` has been placed in this directory. You are now ready to `vagrant up` your first virtual environment! Please read the comments in the Vagrantfile as well as documentation on…

MySQL概述以及下载安装

MySQL5.7概述以及下载安装&#xff08;centOS7&#xff09; 一、MySQL简介 MySQL是一个典型的关系数据库&#xff0c;目前是Oracle公司产品之一&#xff0c;也是目前主流使用的关系型数据库之一。使用MySQL可以进行最基本的数据存储、管理、查询等操作&#xff0c;也可以方便的…

中粮凤凰里共有产权看房记

中粮凤凰里看房是希望而来&#xff0c;失望而归。主要是对如下失望&#xff0c;下述仅个人看房感受&#xff1a; 1. 户型不喜欢&#xff1a;三房的厨房和餐厅位置很奇葩 2. 样板间在25楼&#xff1a;湖景一言难尽和有工厂噪声 3. 精装修的交房质量:阳台的推拉门用料很草率 …

轮播(css+js)

目录 1.实现效果 2.基础代码演示 2.1js代码 2.1css样式 2.3实现效果 3.实现点击切换 3.1给button添加点击事件 3.2效果图如下 3.3发现问题 3.3.1不循环 3.3.2循环 1.实现效果 2.基础代码演示 2.1js代码 <div class"out-box"><div class"tes…

深度学习(2)前向传播与反向传播

这一次我们重点讲解前向传播与反向传播&#xff0c;对这里还是有点糊涂 前向传播&#xff08;Forward Propagation&#xff09;和反向传播&#xff08;Backward Propagation&#xff09;是深度学习中神经网络训练的核心过程。它们分别负责计算神经网络的输出以及更新神经网络的…

HTML5 拖拽 API 深度解析

一、HTML5 拖拽 API 深度解析 1.1 背景与发展 HTML5 的拖拽 API 是为了解决传统拖拽操作复杂而设计的。传统方法依赖鼠标事件和复杂的逻辑计算&#xff0c;而 HTML5 提供了标准化的拖拽事件和数据传递机制&#xff0c;使得开发者能够快速实现从一个元素拖拽到另一个元素的交互…

前端自己也能开启HTTPS

目录 前言 使用mkcert 安装 创建证书 利用 mkcert 创建 ca 根据 ca 创建 cert 安装证书 项目开启HTTPS 安装插件 配置 vitecofnig.js 最终效果 前言 今天我发现了一个宝藏&#xff0c;兄弟们&#xff01;就是前端开发阶段是可以使用https来开发的。对不懂前端的后端兄…

精通 Python 网络安全

与 FTP、SSH 和 SNMP 服务器交互 本章将帮助您了解允许我们与 FTP、SSH 和 SNMP 服务器交互的模块。在本章中&#xff0c;我们将探讨网络中的计算机如何相互交互。一些允许我们连接 FTP、SSH 和 SNMP 服务器的工具可以在 Python 中找到&#xff0c;其中我们可以突出显示 FTPLi…

【C++跬步积累】 —— 二叉搜索树(模拟实现+源代码)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;C跬步积累 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日一题 &#x1f7e1; Linux跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0…