【Java设计模式 设计模式与范式】结构型模式 六:组合模式

简介: 【Java设计模式 设计模式与范式】结构型模式 六:组合模式

本篇Blog继续学习结构型模式,了解如何更优雅的布局类和对象。结构型模式描述如何将类或对象按某种布局组合以便获得更好、更灵活的结构。虽然面向对象的继承机制提供了最基本的子类扩展父类的功能,但结构型模式不仅仅简单地使用继承,而更多地通过组合与运行期的动态组合来实现更灵活的功能。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。本篇学习的是组合模式。由于学习的都是设计模式,所有系列文章都遵循如下的目录:

  • 模式档案:包含模式的定义、模式的特点、解决什么问题、优缺点、使用场景等
  • 模式结构:包含模式的角色定义及调用关系以及其模版代码
  • 模式示例:包含模式的实现方式代码举例,生活中的简单问题映射
  • 模式实践:如果工作中或开源项目用到了该模式,就将使用过程贴到这里,并且客观讨论使用的是否恰当
  • 模式对比:如果模式相似,有必要体现其相似点及不同点,区分使用,说明哪些场景下使用哪种模式比较好
  • 模式扩展:如果模式有与标准结构定义不同的变体形式,一并体现出其变体结构

接下来所有设计模式的介绍都暂且遵循此基本行文逻辑吗,如果某一条目没有则无需体现,但条目顺序遵循此结构

模式档案

在现实生活中,存在很多部分-整体的关系,例如,大学中的部门与学院、总公司中的部门与分公司等等,在软件开发中也是这样,例如,文件系统中的文件与文件夹窗体程序中的简单控件与容器控件等。对这些简单对象与复合对象的处理,如果用组合模式来实现会很方便。

模式定义:组合模式将一组对象组织(Compose)成树形结构,以表示一种部分 - 整体的层次结构。组合让客户端调用方可以统一单个对象和组合对象的处理逻辑

优点:组合模式有如下两个优点:

  1. 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,简化了客户端代码
  2. 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足开闭原则

缺点:使用场景较窄,只有处理的数据对象为树形结构才能发挥作用,通常可以应用于组织架构设计。

使用场景数据必须能表示成树形结构才能使用组合模式,这也导致了这种模式在实际的项目开发中并不那么常用。但是,一旦数据满足树形结构,应用这种模式就能发挥很大的作用,能让代码变得非常简洁

组合模式跟面向对象设计中的组合关系(通过组合来组装两个类),完全是两码事。这里讲的组合模式,主要是用来处理树形结构数据。这里的数据,可以简单理解为一组对象集合

模式结构

组合模式包含以下主要角色。

  • 抽象组件(Component)角色:它的主要作用是为树叶组件和树枝组件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象组件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝组件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
  • 树叶组件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象组件。
  • 树枝组件(Composite)角色 / 中间组件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象组件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

组合模式分为透明式的组合模式安全式的组合模式。透明式的组合模式结构如下:

1 透明式组合模式

在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题

可以看到树叶组件也实现了管理的方法,但是方法为空方法

2 安全式组合模式

在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性

透明式和安全式的组合模式,唯一的区别就是抽象组件中是否添加树叶节点的管理方法:

  • 透明式添加,那么在基于接口进行使用时,使用者并不需要关心实际对象是树枝还是树叶
  • 组合式不添加,那么在基于接口进行使用时,要想使用树叶管理方法,还得声明对象为树枝对象,因为树叶对象并不具备管理方法。

这是二者的一个本质区别。

模式实现

分别实现透明式组合模式和安全式组合模式

透明式组合模式

各个角色代码如下:

1 抽象组件

//抽象构件
interface Component {
    public void add(Component c);
    public void remove(Component c);
    public Component getChild(int i);
    public void operation();
}

2 树叶组件

//树叶构件
class Leaf implements Component {
    private String name;
    public Leaf(String name) {
        this.name = name;
    }
    public void add(Component c) {
    }
    public void remove(Component c) {
    }
    public Component getChild(int i) {
        return null;
    }
    public void operation() {
        System.out.println("树叶" + name + ":被访问!");
    }
}

3 树枝组件

//树枝构件
class Composite implements Component {
    private ArrayList<Component> children = new ArrayList<Component>();
    public void add(Component c) {
        children.add(c);
    }
    public void remove(Component c) {
        children.remove(c);
    }
    public Component getChild(int i) {
        return children.get(i);
    }
    public void operation() {
        for (Object obj : children) {
            ((Component) obj).operation();
        }
    }
}

客户端调用如下:

public class CompositePattern {
    public static void main(String[] args) {
        Component twig0 = new Composite();
        Component twig1 = new Composite();
        Component leaf1 = new Leaf("1");
        Component leaf2 = new Leaf("2");
        Component leaf3 = new Leaf("3");
        twig0.add(leaf1);
        twig0.add(twig1);
        twig1.add(leaf2);
        twig1.add(leaf3);
        twig0.operation();
        //空方法,不执行
        leaf1.add(leaf2);
    }
}

打印结果如下:

树叶1:被访问!
树叶2:被访问!
树叶3:被访问!

安全式组合模式

各个角色代码如下:

1 抽象组件

修改 Component 代码,只保留层次的公共行为

//抽象构件
interface Component {
    public void operation();
}

2 树叶组件

//树叶构件
class Leaf implements Component {
    private String name;
    public Leaf(String name) {
        this.name = name;
    }
    public void operation() {
        System.out.println("树叶" + name + ":被访问!");
    }
}

3 树枝组件

//树枝构件
class Composite implements Component {
    private ArrayList<Component> children = new ArrayList<Component>();
    public void add(Component c) {
        children.add(c);
    }
    public void remove(Component c) {
        children.remove(c);
    }
    public Component getChild(int i) {
        return children.get(i);
    }
    public void operation() {
        for (Object obj : children) {
            ((Component) obj).operation();
        }
    }
}

修改客户端代码,将树枝构件类型更改为 Composite 类型,以便获取管理子类操作的方法

public class CompositePattern {
    public static void main(String[] args) {
        Composite twig0 = new Composite();
        Composite twig1 = new Composite();
        Component leaf1 = new Leaf("1");
        Component leaf2 = new Leaf("2");
        Component leaf3 = new Leaf("3");
        twig0.add(leaf1);
        twig0.add(twig1);
        twig1.add(leaf2);
        twig1.add(leaf3);
        twig0.operation();
    }
}

模式实践

我们有如下两个场景的实践:设计一个文件系统设计一个组织成本核算系统,以下实例组合模式为安全式组合模式。

设计一个文件系统

假设我们有这样一个需求:设计一个类来表示文件系统中的目录,能方便地实现下面这些功能:动态地添加、删除某个目录下的子目录或文件;统计指定目录下的文件个数;统计指定目录下的文件总大小。代码如下:

public class FileSystemNode {
  private String path;
  private boolean isFile;
  private List<FileSystemNode> subNodes = new ArrayList<>();
  public FileSystemNode(String path, boolean isFile) {
    this.path = path;
    this.isFile = isFile;
  }
  public int countNumOfFiles() {
    if (isFile) {
      return 1;
    }
    int numOfFiles = 0;
    for (FileSystemNode fileOrDir : subNodes) {
      numOfFiles += fileOrDir.countNumOfFiles();
    }
    return numOfFiles;
  }
  public long countSizeOfFiles() {
    if (isFile) {
      File file = new File(path);
      if (!file.exists()) return 0;
      return file.length();
    }
    long sizeofFiles = 0;
    for (FileSystemNode fileOrDir : subNodes) {
      sizeofFiles += fileOrDir.countSizeOfFiles();
    }
    return sizeofFiles;
  }
  public String getPath() {
    return path;
  }
  public void addSubNode(FileSystemNode fileOrDir) {
    subNodes.add(fileOrDir);
  }
  public void removeSubNode(FileSystemNode fileOrDir) {
    int size = subNodes.size();
    int i = 0;
    for (; i < size; ++i) {
      if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())) {
        break;
      }
    }
    if (i < size) {
      subNodes.remove(i);
    }
  }
}

单纯从功能实现角度来说,上面的代码没有问题,已经实现了我们想要的功能。但是,如果我们开发的是一个大型系统,从扩展性(文件或目录可能会对应不同的操作)、业务建模(文件和目录从业务上是两个概念)、代码的可读性(文件和目录区分对待更加符合人们对业务的认知)的角度来说,我们最好对文件和目录进行区分设计,定义为 File 和 Directory 两个类

public abstract class FileSystemNode {
  protected String path;
  public FileSystemNode(String path) {
    this.path = path;
  }
  public abstract int countNumOfFiles();
  public abstract long countSizeOfFiles();
  public String getPath() {
    return path;
  }
}
public class File extends FileSystemNode {
  public File(String path) {
    super(path);
  }
  @Override
  public int countNumOfFiles() {
    return 1;
  }
  @Override
  public long countSizeOfFiles() {
    java.io.File file = new java.io.File(path);
    if (!file.exists()) return 0;
    return file.length();
  }
}
public class Directory extends FileSystemNode {
  private List<FileSystemNode> subNodes = new ArrayList<>();
  public Directory(String path) {
    super(path);
  }
  @Override
  public int countNumOfFiles() {
    int numOfFiles = 0;
    for (FileSystemNode fileOrDir : subNodes) {
      numOfFiles += fileOrDir.countNumOfFiles();
    }
    return numOfFiles;
  }
  @Override
  public long countSizeOfFiles() {
    long sizeofFiles = 0;
    for (FileSystemNode fileOrDir : subNodes) {
      sizeofFiles += fileOrDir.countSizeOfFiles();
    }
    return sizeofFiles;
  }
  public void addSubNode(FileSystemNode fileOrDir) {
    subNodes.add(fileOrDir);
  }
  public void removeSubNode(FileSystemNode fileOrDir) {
    int size = subNodes.size();
    int i = 0;
    for (; i < size; ++i) {
      if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())) {
        break;
      }
    }
    if (i < size) {
      subNodes.remove(i);
    }
  }
}

设计一个组织成本核算系统

假设我们在开发一个 OA 系统(办公自动化系统)。公司的组织结构包含部门和员工两种数据类型。其中,部门又可以包含子部门和员工

我们希望在内存中构建整个公司的人员架构图(部门、子部门、员工的隶属关系),并且提供接口计算出部门的薪资成本(隶属于这个部门的所有员工的薪资和)。部门包含子部门和员工,这是一种嵌套结构,可以表示成树这种数据结构。计算每个部门的薪资开支这样一个需求,也可以通过在树上的遍历算法来实现。所以,从这个角度来看,这个应用场景可以使用组合模式来设计和实现

抽象组件:人力资源管理

public abstract class HumanResource {
  protected long id;
  protected double salary;
  public HumanResource(long id) {
    this.id = id;
  }
  public long getId() {
    return id;
  }
  public abstract double calculateSalary();
}

树叶节点:人员成本信息

public class Employee extends HumanResource {
  public Employee(long id, double salary) {
    super(id);
    this.salary = salary;
  }
  @Override
  public double calculateSalary() {
    return salary;
  }
}

树枝节点:部门成本信息

public class Department extends HumanResource {
  private List<HumanResource> subNodes = new ArrayList<>();
  public Department(long id) {
    super(id);
  }
  @Override
  public double calculateSalary() {
    double totalSalary = 0;
    for (HumanResource hr : subNodes) {
      //递归计算薪资
      totalSalary += hr.calculateSalary();
    }
    this.salary = totalSalary;
    return totalSalary;
  }
  public void addSubNode(HumanResource hr) {
    subNodes.add(hr);
  }
}

客户端调用逻辑

public class Demo {
  private static final long ORGANIZATION_ROOT_ID = 1001;
  private DepartmentRepo departmentRepo; // 依赖注入
  private EmployeeRepo employeeRepo; // 依赖注入
  public void buildOrganization() {
    Department rootDepartment = new Department(ORGANIZATION_ROOT_ID);
    buildOrganization(rootDepartment);
  }
  // 构建组织架构的代码
  private void buildOrganization(Department department) {
    List<Long> subDepartmentIds = departmentRepo.getSubDepartmentIds(department.getId());
    for (Long subDepartmentId : subDepartmentIds) {
      Department subDepartment = new Department(subDepartmentId);
      department.addSubNode(subDepartment);
      //递归构建组织
      buildOrganization(subDepartment);
    }
    List<Long> employeeIds = employeeRepo.getDepartmentEmployeeIds(department.getId());
    for (Long employeeId : employeeIds) {
      double salary = employeeRepo.getEmployeeSalary(employeeId);
      department.addSubNode(new Employee(employeeId, salary));
    }
  }
}

总结一下

组合模式的场景使用较为特殊,实际上就是树这种数据结构组织的对象,通过抽象复杂数据结构中的通用方法使这个复杂对象调用起来和简单对象一样容易,例如核算一个部门的成本数据,对于部门+人这类组织数据就可以理所当然的认为是一个树形结构,无论是部门这种树枝还是人这种树叶都提供计算成本的统一方法,只要构建完毕这样一个组织,那么无论是计算哪一级的成本,都统一调用计算方法即可,递归逻辑已经内置在组织构建过程中了。

相关文章
|
28天前
|
设计模式 Java Spring
Java 设计模式之责任链模式:优雅处理请求的艺术
责任链模式通过构建处理者链,使请求沿链传递直至被处理,实现发送者与接收者的解耦。适用于审批流程、日志处理等多级处理场景,提升系统灵活性与可扩展性。
197 2
|
28天前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
238 0
|
3月前
|
设计模式 缓存 Java
Java设计模式(二):观察者模式与装饰器模式
本文深入讲解观察者模式与装饰器模式的核心概念及实现方式,涵盖从基础理论到实战应用的全面内容。观察者模式实现对象间松耦合通信,适用于事件通知机制;装饰器模式通过组合方式动态扩展对象功能,避免子类爆炸。文章通过Java示例展示两者在GUI、IO流、Web中间件等场景的应用,并提供常见陷阱与面试高频问题解析,助你写出灵活、可维护的代码。
|
27天前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
240 35
|
27天前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
208 8
|
3月前
|
设计模式 安全 Java
Java设计模式(一):单例模式与工厂模式
本文详解单例模式与工厂模式的核心实现及应用,涵盖饿汉式、懒汉式、双重检查锁、工厂方法、抽象工厂等设计模式,并结合数据库连接池与支付系统实战案例,助你掌握设计模式精髓,提升代码专业性与可维护性。
|
3月前
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。
|
设计模式 缓存 安全
Java设计模式的单例模式应用场景
Java设计模式的单例模式应用场景
258 4
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
188 4
|
设计模式 安全 Java
Java 编程中的设计模式:单例模式的深度解析
【9月更文挑战第22天】在Java的世界里,单例模式就像是一位老练的舞者,轻盈地穿梭在对象创建的舞台上。它确保了一个类仅有一个实例,并提供全局访问点。这不仅仅是代码优雅的体现,更是资源管理的高手。我们将一起探索单例模式的奥秘,从基础实现到高级应用,再到它与现代Java版本的舞蹈,让我们揭开单例模式的面纱,一探究竟。
111 11