组合模式详解以及代码实战

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

简介

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

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

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

image.png

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

好处和坏处

组合模式的好处有:

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

组合模式的坏处有:

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

应用场景

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

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

Java 代码示例

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

定义抽象组件

java

复制代码

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

定义叶子节点

java

复制代码

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);
    }
}

定义组合节点

java

复制代码

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);
        }
    }
}

客户端代码

java

复制代码

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);
    }
}

输出结果:

css

复制代码

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

Go 代码示例

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()
}

输出结果:

makefile

复制代码

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 注解来实现,代码如下:

抽象组件不用改造

java

复制代码

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

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

java

复制代码

@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") 注解

java

复制代码

@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 测试代码

java

复制代码

@Slf4j
@SpringBootTest
class SpringBootTest {
    @Autowired
    private Folder folder;
    @Test
    void test() {
        folder.printPath();
    }
}

输出结果:

css

复制代码

root
a.txt

总结

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

目录
相关文章
|
3月前
|
设计模式 XML Java
【设计模式】装饰器模式(定义 | 特点 | Demo入门讲解)
【设计模式】装饰器模式(定义 | 特点 | Demo入门讲解)
41 0
|
4月前
|
设计模式 Java
Java设计模式:组合模式的介绍及代码演示
组合模式是一种结构型设计模式,用于将多个对象组织成树形结构,并统一处理所有对象。例如,统计公司总人数时,可先统计各部门人数再求和。该模式包括一个通用接口、表示节点的类及其实现类。通过树形结构和节点的通用方法,组合模式使程序更易扩展和维护。
Java设计模式:组合模式的介绍及代码演示
|
5月前
|
设计模式 Java
常用设计模式介绍~~~ Java实现 【概念+案例+代码】
文章提供了一份常用设计模式的全面介绍,包括创建型模式、结构型模式和行为型模式。每种设计模式都有详细的概念讲解、案例说明、代码实例以及运行截图。作者通过这些模式的介绍,旨在帮助读者更好地理解源码、编写更优雅的代码,并进行系统重构。同时,文章还提供了GitHub上的源码地址,方便读者直接访问和学习。
常用设计模式介绍~~~ Java实现 【概念+案例+代码】
|
8月前
|
设计模式 安全 Java
[设计模式Java实现附plantuml源码~结构型]树形结构的处理——组合模式
[设计模式Java实现附plantuml源码~结构型]树形结构的处理——组合模式
|
8月前
|
设计模式 JavaScript 前端开发
[设计模式Java实现附plantuml源码~结构型] 提供统一入口——外观模式
[设计模式Java实现附plantuml源码~结构型] 提供统一入口——外观模式
|
8月前
|
设计模式 存储 Java
23种设计模式,组合模式的概念优缺点以及JAVA代码举例
【4月更文挑战第5天】组合模式(Composite Pattern)是一种结构型设计模式,旨在通过将对象组合成树形结构以表示部分-整体的层次结构,使用户对单个对象和组合对象的使用具有一致性。这种模式让客户可以统一地处理单个对象和组合对象
85 6
|
8月前
|
设计模式 Java
23种设计模式,装饰器模式的概念优缺点以及JAVA代码举例
【4月更文挑战第5天】装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式作为现有类的一个包装。
87 6
|
8月前
|
设计模式 API
【设计模式】什么是外观模式并给出例子!
【设计模式】什么是外观模式并给出例子!
59 0
|
8月前
|
设计模式 算法 Java
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
|
8月前
|
设计模式 存储 Java
[设计模式Java实现附plantuml源码~结构型]实现对象的复用——享元模式
[设计模式Java实现附plantuml源码~结构型]实现对象的复用——享元模式