组合模式详解

组合,模式,详解 · 浏览次数 : 122

小编点评

**抽象组件** 抽象组件不用改造public interface File { // 获取文件名称 String getName(); // 添加子文件 void add(File file); // 删除子文件 void remove(File file); // 获取子文件 List<File> getChildren(); // 打印文件路径 void printPath();} **叶子节点** 叶子节点添加 @Component(\"a.txt\") 注解@Component(\"a.txt\")public class TextFile implements File { private String name; public TextFile() { this.name = \"a.txt\"; } @Override public String getName() { return name; } @Override public void add(File file) { throw new UnsupportedOperationException(\"Text file cannot add child file\"); } @Override public void remove(File file) { throw new UnsupportedOperationException(\"Text file cannot remove child file\"); } @Override public List<File> getChildren() { throw new UnsupportedOperationException(\"Text file has no child file\"); } @Override public void printPath() { System.out.println(name); }} **组合节点** 组合节点添加 @Component(\"root\") 注解@Component(\"root\")public class Folder implements File { private String name; private List<File> children; // 通过@Autowired注解自动注入所有类型为File的Bean对象到children集合中 @Autowired public Folder(List<File> children) { this.name = \"root\"; this.children = children; } @Override public String getName() { return name; } @Override public void add(File file) { children.add(file); } @Override public void remove(File file) { children.remove(file); } @Override public List<File> getChildren() { return children; } @Override public void printPath() { System.out.println(name); for (File child : children) { child.printPath(); } }}

正文

简介

组合模式(Composite)是针对由多个节点对象(部分)组成的树形结构的对象(整体)而发展出的一种结构型设计模式,它能够使客户端在操作整体对象或者其下的每个节点对象时做出统一的响应,保证树形结构对象使用方法的一致性,使客户端不必关注对象的整体或部分,最终达到对象复杂的层次结构与客户端解耦的目的。

组合模式的核心思想是将对象看作是一个树形结构,其中每个节点可以是一个单独的对象(叶子节点)或者一个包含其他节点的容器(组合节点)。叶子节点和组合节点都实现了相同的接口,这样客户端就可以对它们进行一致的操作,而不需要关心它们的具体类型。

组合模式有以下几个角色:

组合模式

  • Component(组件接口):所有复合节点与叶节点的高层抽象,定义出需要对组件操作的接口标准。对应本章例程中的抽象节点类,具体使用接口还是抽象类需根据具体场景而定。
  • Composite(复合组件):包含多个子组件对象(可以是复合组件或叶端组件)的复合型组件,并实现组件接口中定义的操作方法。对应本章例程中作为“根节点/枝节点”的文件夹类。
  • Leaf(叶端组件):不包含子组件的终端组件,同样实现组件接口中定义的操作方法。对应本章例程中作为“叶节点”的文件类
  • Client(客户端):按所需的层级关系部署相关对象并操作组件接口所定义的接口,即可遍历树结构上的所有组件。

好处和坏处

组合模式的好处有:

  • 可以将对象组合成树形结构,表示整体-部分的层次关系,符合人们的直觉。
  • 可以统一处理单个对象和对象组合,简化了客户端的代码逻辑,提高了系统的可复用性。
  • 可以遵循开闭原则,扩展性高,增加新的节点类型时不需要修改原有代码。

组合模式的坏处有:

  • 可以使设计变得过于抽象,不利于理解和维护。
  • 可以违反单一职责原则,让叶子节点和组合节点具有相同的接口,导致叶子节点出现不必要的方法。
  • 可以导致递归调用过深,影响系统的性能。

应用场景

组合模式是一种将对象组合成树形结构的设计模式,它可以表示整体-部分的层次关系,并且提供了一致的接口来操作单个对象和对象组合。应用场景有:

  • 当需要表示一个对象整体与部分的层次结构时,可以使用组合模式来实现树形结构。例如,文件系统中的文件与文件夹、组织机构中的部门与员工、商品分类中的类别与商品等。
  • 当需要统一处理单个对象和对象组合时,可以使用组合模式来实现多态性。例如,图形界面中的简单控件与容器控件、菜单系统中的菜单项与子菜单、报表系统中的单元格与表格等。
  • 当需要将对象的创建和使用分离时,可以使用组合模式来实现依赖注入。例如,Spring框架中的Bean对象与BeanFactory对象、测试框架中的测试用例与测试套件等。

Java 代码示例

假设我们有一个文件系统,其中有两种类型的文件:文本文件和文件夹。文本文件是叶子节点,文件夹是组合节点,可以包含其他文件。我们想要使用组合模式来实现文件系统的层次结构,并且提供一个打印文件路径的方法。代码如下:

定义抽象组件

public interface File {
    // 获取文件名称
    String getName();
    // 添加子文件
    void add(File file);
    // 删除子文件
    void remove(File file);
    // 获取子文件
    List<File> getChildren();
    // 打印文件路径
    void printPath(int space);
}

定义叶子节点

public class TextFile implements File {
    private String name;

    public TextFile(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void add(File file) {
        throw new UnsupportedOperationException("Text file cannot add child file");
    }

    @Override
    public void remove(File file) {
        throw new UnsupportedOperationException("Text file cannot remove child file");
    }

    @Override
    public List<File> getChildren() {
        throw new UnsupportedOperationException("Text file has no child file");
    }

    @Override
    public void printPath(int space) {
        StringBuilder sp = new StringBuilder();
        for (int i = 0; i < space; i++) {
            sp.append(" ");
        }
        System.out.println(sp + name);
    }
}

定义组合节点

public class Folder implements File {
    private String name;
    private List<File> children;

    public Folder(String name) {
        this.name = name;
        children = new ArrayList<>();
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void add(File file) {
        children.add(file);
    }

    @Override
    public void remove(File file) {
        children.remove(file);
    }

    @Override
    public List<File> getChildren() {
        return children;
    }

    @Override
    public void printPath(int space) {
        StringBuilder sp = new StringBuilder();
        for (int i = 0; i < space; i++) {
            sp.append(" ");
        }
        System.out.println(sp + name);
        space += 2;
        for (File child : children) {
            child.printPath(space);
        }
    }
}

客户端代码

public class Client {
    public static void main(String[] args) {
        // 创建一个根文件夹,并添加两个文本文件和一个子文件夹
        File root = new Folder("root");
        root.add(new TextFile("a.txt"));
        root.add(new TextFile("b.txt"));
        File subFolder = new Folder("subFolder");
        root.add(subFolder);

        // 在子文件夹中添加两个文本文件
        subFolder.add(new TextFile("c.txt"));
        subFolder.add(new TextFile("d.txt"));

        // 打印根文件夹的路径
        root.printPath(0);
    }
}

输出结果:

root
  a.txt
  b.txt
  subFolder
    c.txt
    d.txt

Go 代码示例

package main

// importing fmt package
import (
	"fmt"
)

// IComposite interface
type IComposite interface {
	perform()
}

// Leaflet struct
type Leaflet struct {
	name string
}

// Leaflet class method perform
func (leaf *Leaflet) perform() {

	fmt.Println("Leaflet " + leaf.name)
}

// Branch struct
type Branch struct {
	leafs    []Leaflet
	name     string
	branches []Branch
}

// Branch class method perform
func (branch *Branch) perform() {

	fmt.Println("Branch: " + branch.name)
	for _, leaf := range branch.leafs {
		leaf.perform()
	}

	for _, branch := range branch.branches {
		branch.perform()
	}
}

// Branch class method add leaflet
func (branch *Branch) add(leaf Leaflet) {
	branch.leafs = append(branch.leafs, leaf)

}

//Branch class method addBranch branch
func (branch *Branch) addBranch(newBranch Branch) {

	branch.branches = append(branch.branches, newBranch)
}

//Branch class  method getLeaflets
func (branch *Branch) getLeaflets() []Leaflet {
	return branch.leafs
}

// main method
func main() {

	var branch = &Branch{name: "branch 1"}

	var leaf1 = Leaflet{name: "leaf 1"}
	var leaf2 = Leaflet{name: "leaf 2"}

	var branch2 = Branch{name: "branch 2"}

	branch.add(leaf1)
	branch.add(leaf2)
	branch.addBranch(branch2)

	branch.perform()

}

输出结果:

G:\GoLang\examples>go run composite.go
Branch: branch 1
Leaflet leaf 1
Leaflet leaf 2
Branch: branch 2

Spring 代码示例

Spring 框架也可以使用组合模式来实现对象的层次结构,它提供了一个注解叫做 @Component,它可以用来标注一个类是一个组件,即一个可被Spring管理的Bean对象。@Component 注解有一个属性叫做value,它可以用来指定组件的名称。我们可以使用 @Component 注解来标注我们的文件类,然后在配置文件或注解中声明这些组件,Spring 就会自动创建和管理这些组件对象。

假设我们有一个文件系统,其中有两种类型的文件:文本文件和文件夹。文本文件是叶子节点,文件夹是组合节点,可以包含其他文件。我们想要使用组合模式来实现文件系统的层次结构,并且提供一个打印文件路径的方法。我们可以使用 @Component 注解来实现,代码如下:

抽象组件不用改造

public interface File {
    // 获取文件名称
    String getName();
    // 添加子文件
    void add(File file);
    // 删除子文件
    void remove(File file);
    // 获取子文件
    List<File> getChildren();
    // 打印文件路径
    void printPath();
}

叶子节点添加 @Component("a.txt") 注解

@Component("a.txt")
public class TextFile implements File {
    private String name;

    public TextFile() {
        this.name = "a.txt";
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void add(File file) {
        throw new UnsupportedOperationException("Text file cannot add child file");
    }

    @Override
    public void remove(File file) {
        throw new UnsupportedOperationException("Text file cannot remove child file");
    }

    @Override
    public List<File> getChildren() {
        throw new UnsupportedOperationException("Text file has no child file");
    }

    @Override
    public void printPath() {
        System.out.println(name);
    }
}

组合节点添加 @Component("root") 注解

@Component("root")
public class Folder implements File {
    private String name;
    private List<File> children;

    // 通过@Autowired注解自动注入所有类型为File的Bean对象到children集合中
    @Autowired
    public Folder(List<File> children) {
        this.name = "root";
        this.children = children;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void add(File file) {
        children.add(file);
    }

    @Override
    public void remove(File file) {
        children.remove(file);
    }

    @Override
    public List<File> getChildren() {
        return children;
    }

    @Override
    public void printPath() {
        System.out.println(name);
        for (File child : children) {
            child.printPath();
        }
    }
}

SpringBoot 测试代码

@Slf4j
@SpringBootTest
class SpringBootTest {

    @Autowired
    private Folder folder;

    @Test
    void test() {
        folder.printPath();
    }
}

输出结果:

root
a.txt

总结

组合模式是一种常用的结构型设计模式,它可以将对象组合成树形结构,并且提供了一致的接口来操作单个对象和对象组合,是一种值得学习和掌握的设计模式。

关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、高效开发工具等,您的关注将是我的更新动力!

与组合模式详解相似的内容:

组合模式详解

## 简介 组合模式(Composite)是针对由多个节点对象(部分)组成的树形结构的对象(整体)而发展出的一种结构型设计模式,它能够使客户端在操作整体对象或者其下的每个节点对象时做出统一的响应,保证树形结构对象使用方法的一致性,使客户端不必关注对象的整体或部分,最终达到对象复杂的层次结构与客户端解

详解C#委托与事件

在C#中,委托是一种引用类型的数据类型,允许我们封装方法的引用。通过使用委托,我们可以将方法作为参数传递给其他方法,或者将多个方法组合在一起,从而实现更灵活的编程模式。委托类似于函数指针,但提供了类型安全和垃圾回收等现代语言特性。 基本概念 定义委托 定义委托需要指定它所代表的方法的原型,包括返回类

详解数仓的向量化执行引擎

本文分享自华为云社区《GaussDB(DWS)向量化执行引擎详解》,作者: yd_212508532。 前言 适用版本:【基线功能】 传统的行执行引擎大多采用一次一元组的执行模式,这样在执行过程中CPU大部分时间并没有用来处理数据,更多的是在遍历执行树,就会导致CPU的有效利用率较低。而在面对OLA

机器学习策略篇:详解如何改善你的模型的表现(Improving your model performance)

如何改善模型的表现 学过正交化,如何设立开发集和测试集,用人类水平错误率来估计贝叶斯错误率以及如何估计可避免偏差和方差。现在把它们全部组合起来写成一套指导方针,如何提高学习算法性能的指导方针。 所以想要让一个监督学习算法达到实用,基本上希望或者假设可以完成两件事情。首先,的算法对训练集的拟合很好,这

DTSE Tech Talk | 第9期:EiPaaS驱动企业数字化转型

摘要: 揭秘华为企业集成新模式。 本期直播详解 组装式概念解析 EiPaaS的核心技术能力 华为实践经验分享 EiPaaS未来的技术趋势 直播讲师:华为云PaaS DTSE布道师 傅翌伟 tips:EiPaaS全称:Enterprise integration Platform as a Servi

Vue 3 组件基础与模板语法详解

title: Vue 3 组件基础与模板语法详解 date: 2024/5/24 16:31:13 updated: 2024/5/24 16:31:13 categories: 前端开发 tags: Vue3特性 CompositionAPI Teleport Suspense Vue3安装 组件

NIO的三大核心组件详解,充分说明为什么NIO在网络IO中拥有高性能!

一、写在开头 我们在上一篇博文中提到了Java IO中常见得三大模型(BIO,NIO,AIO),其中NIO是我们在日常开发中使用比较多的一种IO模型,我们今天就一起来详细的学习一下。 在传统的IO中,多以这种同步阻塞的IO模型为主,程序发起IO请求后,处理线程处于阻塞状态,直到请求的IO数据从内核空

详解共识算法的Raft算法模拟数

摘要:Raft算法是一种分布式共识算法,用于解决分布式系统中的一致性问题。 本文分享自华为云社区《共识算法之Raft算法模拟数》,作者: TiAmoZhang 。 01、Leader选举 存在A、B、C三个成员组成的Raft集群,刚启动时,每个成员都处于Follower状态,其中,成员A心跳超时为1

vue3组件通信与props

title: vue3组件通信与props date: 2024/5/31 下午9:00:57 updated: 2024/5/31 下午9:00:57 categories: 前端开发 tags: Vue3组件 Props详解 生命周期 数据通信 模板语法 Composition API 单向数据

Nomad系列-Nomad网络模式

系列文章 Nomad 系列文章 概述 Nomad 的网络和 Docker 的也有很大不同, 和 K8s 的有很大不同. 另外, Nomad 不同版本(Nomad 1.3 版本前后)或是否集成 Consul 及 CNI 等不同组件也会导致网络模式各不相同. 本文详细梳理一下 Nomad 的主要几种网络