你不知道的组合模式!如何在复杂项目中灵活处理对象层次结构?

🎯 设计模式专栏,持续更新中, 欢迎订阅:JAVA实现设计模式
🛠️ 希望小伙伴们一键三连,有问题私信都会回复,或者在评论区直接发言

组合模式

组合模式(Composite Pattern)是一种结构型设计模式,它允许将对象组合成树形结构以表示“整体-部分”的层次结构。组合模式使得客户端可以一致地对待单个对象和对象的组合,从而能够对树形结构中的所有对象进行统一的操作。

🎯 核心要点:

  • 树形结构:组合模式通常用于表示层次结构,典型应用场景是文件系统、GUI 组件、组织结构等。
  • 统一接口:无论是叶子对象(Leaf),还是组合对象(Composite),它们都共享相同的接口,客户端可以一致地操作它们。
  • 递归组合:通过递归的方式组合对象,可以在组合对象中嵌套其他组合对象或叶子对象。
  • 透明性:组合模式让客户端不必关心操作的是单个对象还是组合对象,简化了处理。

例子解析:公司组织架构 🌳

假设我们有一个公司组织架构,包含普通员工、经理和部门。部门由经理负责,经理可能管理多个普通员工。通过组合模式,部门可以表示为一个树形结构,每个部门可以包含多个员工或子部门。

组合模式解决的问题

组合模式解决的是如何构建树形结构的问题,使得组合对象和叶子对象都能被统一处理。

  • 简化树形结构中对象的处理,无论它们是单个对象还是组合对象。
  • 解耦客户端代码与复杂元素的内部结构,使得客户端可以统一处理所有类型的节点。

🧩 UML结构图解释

在这里插入图片描述

  • Component(组件接口):定义所有对象的公共接口(如员工的行为接口)。
  • Leaf(叶子节点):实现 Component 接口,表示树形结构中的基础元素(如普通员工)。
  • Composite(组合节点):实现 Component 接口,表示容器对象(如部门),它包含子组件。

Java代码实现:公司组织架构

Step 1: 定义通用的组件接口

// Component 接口
public interface Employee {
    void showEmployeeDetails();
}

Step 2: 实现叶子节点类(普通员工)

// Leaf类,表示普通员工
public class Developer implements Employee {
    private String name;
    private long empId;
    private String position;

    public Developer(long empId, String name, String position) {
        this.empId = empId;
        this.name = name;
        this.position = position;
    }

    @Override
    public void showEmployeeDetails() {
        System.out.println(empId + " " + name + " " + position);
    }
}

Step 3: 实现叶子节点类(经理)

// Leaf类,表示经理
public class Manager implements Employee {
    private String name;
    private long empId;
    private String position;

    public Manager(long empId, String name, String position) {
        this.empId = empId;
        this.name = name;
        this.position = position;
    }

    @Override
    public void showEmployeeDetails() {
        System.out.println(empId + " " + name + " " + position);
    }
}

Step 4: 实现组合节点类(部门)

// Composite类,表示部门
import java.util.ArrayList;
import java.util.List;

public class CompanyDirectory implements Employee {
    private List<Employee> employeeList = new ArrayList<>();

    @Override
    public void showEmployeeDetails() {
        for (Employee emp : employeeList) {
            emp.showEmployeeDetails();
        }
    }

    public void addEmployee(Employee emp) {
        employeeList.add(emp);
    }

    public void removeEmployee(Employee emp) {
        employeeList.remove(emp);
    }
}

Step 5: 使用组合模式来构建公司组织架构

public class Company {
    public static void main(String[] args) {
        // 创建叶子节点:普通员工和经理
        Developer dev1 = new Developer(100, "John", "Senior Developer");
        Developer dev2 = new Developer(101, "Jane", "Junior Developer");
        Manager man1 = new Manager(200, "Mike", "Project Manager");

        // 创建一个部门,并添加员工
        CompanyDirectory engineeringDirectory = new CompanyDirectory();
        engineeringDirectory.addEmployee(dev1);
        engineeringDirectory.addEmployee(dev2);
        engineeringDirectory.addEmployee(man1);

        // 创建另一个部门
        Manager man2 = new Manager(201, "Sara", "HR Manager");
        CompanyDirectory hrDirectory = new CompanyDirectory();
        hrDirectory.addEmployee(man2);

        // 创建公司总目录,并添加部门
        CompanyDirectory companyDirectory = new CompanyDirectory();
        companyDirectory.addEmployee(engineeringDirectory);
        companyDirectory.addEmployee(hrDirectory);

        // 展示所有员工信息
        companyDirectory.showEmployeeDetails();
    }
}

输出结果

100 John Senior Developer
101 Jane Junior Developer
200 Mike Project Manager
201 Sara HR Manager

组织架构UML类图🌳

在这里插入图片描述

解释:

  1. DeveloperManager 类是叶子节点,表示没有子节点的员工。
  2. CompanyDirectory 类是组合节点,它可以包含多个 Employee 对象,这些对象可以是其他 CompanyDirectory(部门)或者 Employee(员工)。
  3. 最终通过树形结构组合了多个员工和部门,可以递归地显示整个公司组织架构。

组合模式在 JDK 源码中的应用

在 Java 集合框架中,MapList 的某些实现也采用了组合模式的思想。比如 java.util.Map 的一些内部实现,通过组合模式将多个 Entry 对象组成 Map

核心类:

  • Map:组件接口,定义了 putget 等基本操作。
  • AbstractMap:抽象组件,实现了基本的 Map 操作。
  • TreeMapHashMap:具体的组合类,通过 Entry 组成一个完整的 Map

代码示例:

import java.util.*;

public class CompositePatternInCollections {
    public static void main(String[] args) {
        // 使用组合模式管理一组键值对(Entry)
        Map<String, String> map = new HashMap<>();
        map.put("key1", "value1");
        map.put("key2", "value2");

        // 展示 Map 中的所有条目
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " = " + entry.getValue());
        }
    }
}

解释:

  • Map 中,每一个键值对被表示为一个 Map.Entry 对象。Entry 对象是 Map 中的基本单元,所有的键值对都通过 Entry 来组织。组合多个 Entry 就组成了 Map,而 Map 也通过这些 Entry 提供了键值对的存储、查询和操作功能

  • Map 是组件接口,Entry 是键值对的表示,HashMap 是组合对象,它包含多个 Entry 组成的树形结构。

HashMap源码分析:组合 Entry 实现 Map

HashMapMap 接口的一个实现,它使用数组 + 链表 + 红黑树的方式存储多个 Entry。这里的 Entry 对象在 HashMap 中被实现为 Node,并通过组合的方式来组织多个 Entry

HashMap 的核心结构:

  • table[] 数组HashMap 使用一个数组 table[] 来存储 Node 对象,每个 Node 实际上是一个 Entry(键值对)。
  • Node 链表:当多个键的哈希值相同(发生冲突)时,这些 Node 会以链表的形式存储在同一个桶中。
  • 红黑树:当链表长度超过一定阈值(默认为 8)时,链表会转换为红黑树,提高查询性能。

源码解析:HashMap.Node

HashMapNode 实现了 Map.Entry 接口,用于存储键值对。

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;       // 键的哈希值
    final K key;          // 键
    V value;              // 值
    Node<K,V> next;       // 指向下一个节点的指针,用于处理哈希冲突的链表

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    // 获取 key
    public final K getKey() { return key; }

    // 获取 value
    public final V getValue() { return value; }

    // 设置 value
    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    // 计算 Entry 的哈希码
    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    // 比较两个 Entry 是否相等
    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

解释

  • NodeHashMap 中的基本存储单元,它实现了 Map.Entry 接口,存储键值对。
  • 每个 Node 包含键、值、键的哈希值,以及指向下一个节点的引用 next,用于解决哈希冲突。
  • next 是实现链表结构的关键,在发生哈希冲突时,链表结构允许多个 Entry 被组合在同一个桶中。

组合 Entry 的方式:链表和红黑树

HashMap 通过组合多个 Node(即 Entry)来处理哈希冲突。

  • 链表结构:当多个键的哈希值相同,它们被放在同一个桶中,并通过 next 引用形成链表。
  • 红黑树结构:如果链表长度超过阈值,链表将被转换为红黑树,以提高性能。

总结:HashMap 中的组合模式思想

  • 组合结构HashMap 使用数组来存储多个 NodeEntry),而每个桶中的 Node 可能通过链表或红黑树的组合来管理多个键值对。这种结构并非完全符合经典的组合模式,但体现了组合数据结构的思想。
  • Map.Entry:每个 Entry(键值对)作为 Map 的基本单元,通过组合 Entry 对象来构成完整的 Map 实现。

虽然 HashMap 本质上没有严格使用经典的组合模式,但其组合结构的设计理念

组合模式的优缺点

优点:

  1. 简化客户端代码:通过统一的接口,客户端可以一致地处理单个对象和组合对象,而不必区分它们的类型。
  2. 更容易扩展:可以轻松地添加新的叶子节点或组合对象,而不会影响现有代码。
  3. 符合开闭原则:通过递归组合和继承,可以动态地构建复杂的树形结构,并通过增加新类型来扩展系统。

缺点:

  1. 复杂性增加:对于简单的层次结构,使用组合模式会增加系统的复杂性,尤其是当树形结构过于复杂时,代码的维护成本会增加。
  2. 不易限制组合:由于组合对象和叶子对象共享相同的接口,很难限制组合的层次或结构深度,可能导致滥用。
  3. 对类型要求不够严格:客户端在使用组合模式时,可能难以知道当前处理的是组合对象还是叶子对象,失去了一些控制能力
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coffee_baby

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值