🎯 设计模式专栏,持续更新中, 欢迎订阅: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类图🌳
解释:
Developer
和Manager
类是叶子节点,表示没有子节点的员工。CompanyDirectory
类是组合节点,它可以包含多个Employee
对象,这些对象可以是其他CompanyDirectory
(部门)或者Employee
(员工)。- 最终通过树形结构组合了多个员工和部门,可以递归地显示整个公司组织架构。
组合模式在 JDK 源码中的应用
在 Java 集合框架中,Map
和 List
的某些实现也采用了组合模式的思想。比如 java.util.Map
的一些内部实现,通过组合模式将多个 Entry
对象组成 Map
核心类:
Map
:组件接口,定义了put
、get
等基本操作。AbstractMap
:抽象组件,实现了基本的Map
操作。TreeMap
和HashMap
:具体的组合类,通过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
HashMap
是 Map
接口的一个实现,它使用数组 + 链表 + 红黑树的方式存储多个 Entry
。这里的 Entry
对象在 HashMap
中被实现为 Node
,并通过组合的方式来组织多个 Entry
。
HashMap
的核心结构:
table[]
数组:HashMap
使用一个数组table[]
来存储Node
对象,每个Node
实际上是一个Entry
(键值对)。Node
链表:当多个键的哈希值相同(发生冲突)时,这些Node
会以链表的形式存储在同一个桶中。- 红黑树:当链表长度超过一定阈值(默认为 8)时,链表会转换为红黑树,提高查询性能。
源码解析:HashMap.Node
HashMap
的 Node
实现了 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;
}
}
解释:
Node
是HashMap
中的基本存储单元,它实现了Map.Entry
接口,存储键值对。- 每个
Node
包含键、值、键的哈希值,以及指向下一个节点的引用next
,用于解决哈希冲突。 next
是实现链表结构的关键,在发生哈希冲突时,链表结构允许多个Entry
被组合在同一个桶中。
组合 Entry
的方式:链表和红黑树
HashMap
通过组合多个 Node
(即 Entry
)来处理哈希冲突。
- 链表结构:当多个键的哈希值相同,它们被放在同一个桶中,并通过
next
引用形成链表。 - 红黑树结构:如果链表长度超过阈值,链表将被转换为红黑树,以提高性能。
总结:HashMap
中的组合模式思想
- 组合结构:
HashMap
使用数组来存储多个Node
(Entry
),而每个桶中的Node
可能通过链表或红黑树的组合来管理多个键值对。这种结构并非完全符合经典的组合模式,但体现了组合数据结构的思想。 Map.Entry
:每个Entry
(键值对)作为Map
的基本单元,通过组合Entry
对象来构成完整的Map
实现。
虽然 HashMap
本质上没有严格使用经典的组合模式,但其组合结构的设计理念
组合模式的优缺点
优点:
- 简化客户端代码:通过统一的接口,客户端可以一致地处理单个对象和组合对象,而不必区分它们的类型。
- 更容易扩展:可以轻松地添加新的叶子节点或组合对象,而不会影响现有代码。
- 符合开闭原则:通过递归组合和继承,可以动态地构建复杂的树形结构,并通过增加新类型来扩展系统。
缺点:
- 复杂性增加:对于简单的层次结构,使用组合模式会增加系统的复杂性,尤其是当树形结构过于复杂时,代码的维护成本会增加。
- 不易限制组合:由于组合对象和叶子对象共享相同的接口,很难限制组合的层次或结构深度,可能导致滥用。
- 对类型要求不够严格:客户端在使用组合模式时,可能难以知道当前处理的是组合对象还是叶子对象,失去了一些控制能力