设计模式之六:组合模式------构建灵活网络拓扑的艺术
一、什么是组合模式?
1.1 核心定义
组合模式(Composite Pattern) 是一种结构型设计模式 ,它允许你将对象组合成树形结构 来表示"部分-整体"的层次结构。组合模式使得客户端可以统一处理单个对象和组合对象,无需关心处理的是单个元素还是整个组合结构。
1.2 设计意图
- 将对象组合成树形结构,以表示"部分-整体"的层次关系
- 客户端代码统一性:客户端可以一致地使用组合结构和单个对象
- 递归组合:组合对象可以包含其他组合对象,形成树形结构
二、模式结构解析
2.1 UML 类图
┌─────────────────────────┐
│ Component │
├─────────────────────────┤
│ + operation() │
│ + add(Component) │
│ + remove(Component) │
│ + getChild(int) │
└─────────────────────────┘
△
│
┌──────┴───────┐
│ │
┌─────────┐ ┌──────────┐
│ Leaf │ │ Composite│
├─────────┤ ├──────────┤
│ │ │- children│
│ │ │ │
└─────────┘ └──────────┘
2.2 核心角色
| 角色 | 说明 | 职责 |
|---|---|---|
| Component | 抽象组件 | 定义组合对象和叶子对象的共同接口 |
| Leaf | 叶子节点 | 表示组合中的叶子对象,没有子节点 |
| Composite | 组合节点 | 存储子组件,实现与子组件相关的操作 |
三、网关拓扑场景的完美契合
在网关设备管理系统中,组合模式天然适合:
- 主网关 ← Composite:可以包含从网关和STA
- 从网关 ← Composite:可以包含其他从网关和STA
- STA设备 ← Leaf:终端设备,不能再包含其他设备
四、完整代码实现:网关拓扑系统
4.1 抽象组件:网络设备接口
java
/**
* 抽象组件:网络设备接口
* 定义所有网络设备的通用行为
*/
public abstract class NetworkDevice {
protected String deviceId;
protected String name;
protected String type;
protected String ip;
protected String mac;
protected boolean online;
// 构造函数
public NetworkDevice(String deviceId, String name, String ip, String mac) {
this.deviceId = deviceId;
this.name = name;
this.ip = ip;
this.mac = mac;
this.online = true;
}
// ================= 通用操作(所有设备都有的)=================
/**
* 显示设备信息(递归显示)
*/
public abstract void display(int depth);
/**
* 获取设备总数(递归统计)
*/
public abstract int getTotalDevices();
/**
* 获取在线设备数(递归统计)
*/
public abstract int getOnlineDevices();
/**
* 查找设备(递归查找)
*/
public abstract NetworkDevice findDevice(String deviceId);
/**
* 获取设备基本信息
*/
public String getBasicInfo() {
return String.format(
"设备ID: %s\n名称: %s\n类型: %s\nIP: %s\nMAC: %s\n状态: %s",
deviceId, name, type, ip, mac, online ? "在线" : "离线"
);
}
// ================= 默认实现(叶子节点不需要这些)=================
/**
* 添加子设备(默认抛出异常)
*/
public void addDevice(NetworkDevice device) {
throw new UnsupportedOperationException(name + "不支持添加子设备");
}
/**
* 移除子设备(默认抛出异常)
*/
public void removeDevice(String deviceId) {
throw new UnsupportedOperationException(name + "不支持移除子设备");
}
/**
* 获取子设备列表(默认抛出异常)
*/
public List<NetworkDevice> getChildren() {
throw new UnsupportedOperationException(name + "没有子设备");
}
// ================= 工具方法 =================
/**
* 获取缩进字符串
*/
protected String getIndent(int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append("│ ");
}
return sb.toString();
}
/**
* 获取状态图标
*/
protected String getStatusIcon() {
return online ? "🟢" : "🔴";
}
// ================= Getter/Setter =================
public String getDeviceId() { return deviceId; }
public String getName() { return name; }
public String getType() { return type; }
public String getIp() { return ip; }
public String getMac() { return mac; }
public boolean isOnline() { return online; }
public void setOnline(boolean online) { this.online = online; }
public void setName(String name) { this.name = name; }
}
4.2 叶子节点:STA终端设备
java
import java.util.Date;
/**
* 叶子节点:STA终端设备
* 不能再挂载其他设备
*/
public class STADevice extends NetworkDevice {
private String model; // 设备型号
private String firmware; // 固件版本
private double uploadMB; // 上传数据量(MB)
private double downloadMB; // 下载数据量(MB)
private Date lastActive; // 最后活跃时间
private int signalStrength; // 信号强度(0-100)
public STADevice(String deviceId, String name, String ip, String mac,
String model, String firmware) {
super(deviceId, name, ip, mac);
this.type = "STA终端";
this.model = model;
this.firmware = firmware;
this.uploadMB = 0;
this.downloadMB = 0;
this.lastActive = new Date();
this.signalStrength = 100;
}
@Override
public void display(int depth) {
String indent = getIndent(depth);
String signalIcon = getSignalIcon();
System.out.println(indent + "└── " + getStatusIcon() + signalIcon +
" " + name + " [" + type + "]");
System.out.println(indent + " ├── ID: " + deviceId);
System.out.println(indent + " ├── IP: " + ip);
System.out.println(indent + " ├── 信号: " + signalStrength + "%");
System.out.println(indent + " ├── 型号: " + model);
System.out.println(indent + " ├── 固件: " + firmware);
System.out.println(indent + " └── 流量: ↑" +
String.format("%.2f", uploadMB) + "MB / ↓" +
String.format("%.2f", downloadMB) + "MB");
}
@Override
public int getTotalDevices() {
return 1; // STA设备只统计自己
}
@Override
public int getOnlineDevices() {
return online ? 1 : 0;
}
@Override
public NetworkDevice findDevice(String deviceId) {
return this.deviceId.equals(deviceId) ? this : null;
}
// ================= STA特有方法 =================
/**
* 更新流量统计
*/
public void updateTraffic(double upload, double download) {
this.uploadMB += upload;
this.downloadMB += download;
this.lastActive = new Date();
}
/**
* 设置信号强度
*/
public void setSignalStrength(int strength) {
this.signalStrength = Math.max(0, Math.min(100, strength));
}
/**
* 获取详细设备信息
*/
public String getDetailInfo() {
return getBasicInfo() + String.format(
"\n型号: %s\n固件: %s\n信号强度: %d%%\n上传流量: %.2f MB\n下载流量: %.2f MB\n最后活跃: %s",
model, firmware, signalStrength, uploadMB, downloadMB, lastActive
);
}
/**
* 获取信号图标
*/
private String getSignalIcon() {
if (signalStrength >= 80) return "📶";
if (signalStrength >= 50) return "📶";
if (signalStrength >= 20) return "📶";
return "📶";
}
// Getters
public String getModel() { return model; }
public String getFirmware() { return firmware; }
public double getUploadMB() { return uploadMB; }
public double getDownloadMB() { return downloadMB; }
public int getSignalStrength() { return signalStrength; }
}
4.3 组合节点:网关抽象类
java
import java.util.ArrayList;
import java.util.List;
/**
* 组合节点:网关抽象类
* 可以挂载其他网关和STA设备
*/
public abstract class Gateway extends NetworkDevice {
protected List<NetworkDevice> children;
protected int maxConnections; // 最大连接数
protected int currentConnections; // 当前连接数
protected double cpuUsage; // CPU使用率
protected double memoryUsage; // 内存使用率
public Gateway(String deviceId, String name, String ip, String mac,
int maxConnections) {
super(deviceId, name, ip, mac);
this.children = new ArrayList<>();
this.maxConnections = maxConnections;
this.currentConnections = 0;
this.cpuUsage = 0.0;
this.memoryUsage = 0.0;
}
@Override
public void display(int depth) {
String indent = getIndent(depth);
String gatewayIcon = getGatewayIcon();
// 显示网关信息
System.out.println(indent + gatewayIcon + " " + name + " [" + type + "]");
System.out.println(indent + "├── ID: " + deviceId);
System.out.println(indent + "├── IP: " + ip);
System.out.println(indent + "├── 连接: " + currentConnections + "/" + maxConnections);
System.out.println(indent + "├── 资源: CPU " +
String.format("%.1f", cpuUsage) + "%, 内存 " +
String.format("%.1f", memoryUsage) + "%");
System.out.println(indent + "├── 子设备: " + children.size() +
"个 (在线: " + getOnlineDevices() + ")");
// 递归显示子设备
if (!children.isEmpty()) {
System.out.println(indent + "└── 子设备列表:");
for (int i = 0; i < children.size(); i++) {
boolean isLast = (i == children.size() - 1);
String childIndent = indent + (isLast ? " " : "│ ");
System.out.print(childIndent);
children.get(i).display(depth + 1);
}
}
}
@Override
public int getTotalDevices() {
int count = 1; // 统计自己
for (NetworkDevice child : children) {
count += child.getTotalDevices();
}
return count;
}
@Override
public int getOnlineDevices() {
int count = online ? 1 : 0; // 统计自己
for (NetworkDevice child : children) {
count += child.getOnlineDevices();
}
return count;
}
@Override
public NetworkDevice findDevice(String deviceId) {
// 先检查自己
if (this.deviceId.equals(deviceId)) {
return this;
}
// 递归查找子设备
for (NetworkDevice child : children) {
NetworkDevice found = child.findDevice(deviceId);
if (found != null) {
return found;
}
}
return null;
}
// ================= 重写组合节点特有的方法 =================
@Override
public void addDevice(NetworkDevice device) {
if (currentConnections >= maxConnections) {
throw new IllegalStateException(name + "已达到最大连接数限制");
}
// 检查设备是否已存在
for (NetworkDevice child : children) {
if (child.getDeviceId().equals(device.getDeviceId())) {
throw new IllegalArgumentException("设备ID " + device.getDeviceId() + " 已存在");
}
}
children.add(device);
currentConnections++;
updateResourceUsage();
}
@Override
public void removeDevice(String deviceId) {
for (int i = 0; i < children.size(); i++) {
if (children.get(i).getDeviceId().equals(deviceId)) {
children.remove(i);
currentConnections--;
updateResourceUsage();
return;
}
}
throw new IllegalArgumentException("未找到设备ID: " + deviceId);
}
@Override
public List<NetworkDevice> getChildren() {
return new ArrayList<>(children);
}
// ================= 网关特有方法 =================
/**
* 更新资源使用率(根据连接数模拟计算)
*/
protected void updateResourceUsage() {
this.cpuUsage = Math.min(100.0, 5.0 + currentConnections * 3.0);
this.memoryUsage = Math.min(100.0, 10.0 + currentConnections * 2.5);
}
/**
* 获取所有STA设备
*/
public List<STADevice> getAllSTADevices() {
List<STADevice> staList = new ArrayList<>();
collectSTADevices(this, staList);
return staList;
}
private void collectSTADevices(NetworkDevice device, List<STADevice> result) {
if (device instanceof STADevice) {
result.add((STADevice) device);
} else if (device instanceof Gateway) {
for (NetworkDevice child : ((Gateway) device).children) {
collectSTADevices(child, result);
}
}
}
/**
* 拓扑统计信息
*/
public String getTopologyStats() {
int totalDevices = getTotalDevices();
int onlineDevices = getOnlineDevices();
int staCount = getAllSTADevices().size();
int gatewayCount = totalDevices - staCount;
return String.format(
"=== 拓扑统计 ===\n" +
"总设备数: %d\n" +
"在线设备: %d\n" +
"网关设备: %d\n" +
"STA设备: %d\n" +
"连接率: %.1f%%",
totalDevices, onlineDevices, gatewayCount, staCount,
(currentConnections * 100.0 / maxConnections)
);
}
/**
* 获取网关图标(由子类实现)
*/
protected abstract String getGatewayIcon();
}
4.4 具体网关实现
java
/**
* 主网关实现
*/
public class MasterGateway extends Gateway {
private String areaCode; // 区域编码
private String admin; // 管理员
private Date deployTime; // 部署时间
public MasterGateway(String deviceId, String name, String ip, String mac,
int maxConnections, String areaCode, String admin) {
super(deviceId, name, ip, mac, maxConnections);
this.type = "主网关";
this.areaCode = areaCode;
this.admin = admin;
this.deployTime = new Date();
}
@Override
protected String getGatewayIcon() {
return "🏠";
}
@Override
public void display(int depth) {
System.out.println("\n" + "=".repeat(70));
System.out.println("主网关拓扑结构 - " + name + " [" + areaCode + "]");
System.out.println("管理员: " + admin + " | 部署时间: " + deployTime);
System.out.println("=".repeat(70));
super.display(0);
System.out.println("=".repeat(70));
}
/**
* 获取所有从网关
*/
public List<SlaveGateway> getAllSlaveGateways() {
List<SlaveGateway> slaves = new ArrayList<>();
collectSlaveGateways(this, slaves);
return slaves;
}
private void collectSlaveGateways(NetworkDevice device, List<SlaveGateway> result) {
if (device instanceof SlaveGateway) {
result.add((SlaveGateway) device);
}
if (device instanceof Gateway) {
for (NetworkDevice child : ((Gateway) device).getChildren()) {
collectSlaveGateways(child, result);
}
}
}
/**
* 获取主网关详细信息
*/
public String getMasterInfo() {
return getBasicInfo() + String.format(
"\n区域编码: %s\n管理员: %s\n最大连接数: %d\n当前连接: %d\n部署时间: %s",
areaCode, admin, maxConnections, currentConnections, deployTime
);
}
}
/**
* 从网关实现
*/
public class SlaveGateway extends Gateway {
private String masterId; // 所属主网关ID
private int hopCount; // 跳数(距离主网关的层级)
public SlaveGateway(String deviceId, String name, String ip, String mac,
int maxConnections, String masterId, int hopCount) {
super(deviceId, name, ip, mac, maxConnections);
this.type = "从网关";
this.masterId = masterId;
this.hopCount = hopCount;
}
@Override
protected String getGatewayIcon() {
return "📡";
}
/**
* 获取从网关详细信息
*/
public String getSlaveInfo() {
return getBasicInfo() + String.format(
"\n主网关ID: %s\n跳数: %d\n最大连接数: %d\n当前连接: %d",
masterId, hopCount, maxConnections, currentConnections
);
}
public int getHopCount() {
return hopCount;
}
public String getMasterId() {
return masterId;
}
}
4.5 客户端演示代码
java
/**
* 组合模式客户端演示
*/
public class CompositePatternDemo {
public static void main(String[] args) {
System.out.println("🚀 组合模式演示:网关拓扑管理系统\n");
// ========== 1. 创建拓扑结构 ==========
// 1.1 创建主网关
MasterGateway master = new MasterGateway(
"GW-MASTER-001", "总部核心网关", "10.0.1.1", "00:1A:2B:3C:4D:5E",
200, "AREA-BJ-001", "系统管理员"
);
// 1.2 创建一级从网关
SlaveGateway buildingA = new SlaveGateway(
"GW-SLAVE-A01", "A栋网关", "10.0.2.1", "00:1A:2B:3C:4D:10",
50, "GW-MASTER-001", 1
);
SlaveGateway buildingB = new SlaveGateway(
"GW-SLAVE-B01", "B栋网关", "10.0.3.1", "00:1A:2B:3C:4D:20",
30, "GW-MASTER-001", 1
);
// 1.3 创建二级从网关
SlaveGateway floor3 = new SlaveGateway(
"GW-SLAVE-A03", "A栋3层网关", "10.0.2.3", "00:1A:2B:3C:4D:11",
20, "GW-SLAVE-A01", 2
);
// 1.4 创建STA设备
STADevice server1 = new STADevice(
"STA-SRV-001", "数据库服务器", "10.0.1.10", "00:1B:2C:3D:4E:5F",
"Dell R740", "v2.5.1"
);
STADevice pc1 = new STADevice(
"STA-PC-001", "开发工作站", "10.0.2.101", "00:1B:2C:3D:4E:70",
"HP Z4", "Win10 v2004"
);
STADevice camera1 = new STADevice(
"STA-CAM-001", "监控摄像头", "10.0.2.201", "00:1B:2C:3D:4E:80",
"HikVision DS-2CD", "v5.6.5"
);
STADevice printer1 = new STADevice(
"STA-PRT-001", "网络打印机", "10.0.3.50", "00:1B:2C:3D:4E:90",
"HP LaserJet", "v1.8.3"
);
// ========== 2. 构建拓扑关系 ==========
System.out.println("🔗 构建拓扑关系...");
// 主网关直连设备
master.addDevice(buildingA);
master.addDevice(buildingB);
master.addDevice(server1);
// A栋网关连接设备
buildingA.addDevice(floor3);
buildingA.addDevice(pc1);
buildingA.addDevice(camera1);
// B栋网关连接设备
buildingB.addDevice(printer1);
// 3层网关连接设备
STADevice meetingRoom = new STADevice(
"STA-MR-001", "会议室终端", "10.0.2.31", "00:1B:2C:3D:4E:99",
"Conference System", "v3.2.1"
);
floor3.addDevice(meetingRoom);
// ========== 3. 设置设备状态 ==========
System.out.println("⚙️ 设置设备状态...");
printer1.setOnline(false); // 打印机离线
camera1.setSignalStrength(45); // 摄像头信号中等
meetingRoom.updateTraffic(125.5, 320.8); // 会议室终端有流量
// ========== 4. 显示完整拓扑 ==========
master.display(0);
// ========== 5. 统一接口演示 ==========
System.out.println("\n🎯 统一接口演示");
System.out.println("========================================");
// 创建测试设备数组
NetworkDevice[] testDevices = {
master, // 主网关
buildingA, // 从网关
server1 // STA设备
};
for (NetworkDevice device : testDevices) {
System.out.println("\n设备类型: " + device.getType());
System.out.println("设备名称: " + device.getName());
System.out.println("在线状态: " + (device.isOnline() ? "🟢 在线" : "🔴 离线"));
System.out.println("设备总数: " + device.getTotalDevices());
System.out.println("在线设备: " + device.getOnlineDevices());
System.out.println("---");
}
// ========== 6. 拓扑统计 ==========
System.out.println("\n📊 拓扑统计信息");
System.out.println("========================================");
System.out.println(master.getTopologyStats());
// ========== 7. 设备查找演示 ==========
System.out.println("\n🔍 设备查找演示");
System.out.println("========================================");
String searchId = "STA-MR-001";
NetworkDevice found = master.findDevice(searchId);
if (found != null) {
System.out.println("找到设备: " + found.getName());
if (found instanceof STADevice) {
System.out.println("详细信息:\n" + ((STADevice) found).getDetailInfo());
}
}
// ========== 8. 批量操作演示 ==========
System.out.println("\n🔄 批量操作演示");
System.out.println("========================================");
// 8.1 获取所有STA设备
List<STADevice> allSTAs = master.getAllSTADevices();
System.out.println("所有STA设备 (" + allSTAs.size() + "台):");
for (STADevice sta : allSTAs) {
System.out.println(" • " + sta.getName() +
" (信号: " + sta.getSignalStrength() + "%)");
}
// 8.2 获取所有从网关
List<SlaveGateway> allSlaves = master.getAllSlaveGateways();
System.out.println("\n所有从网关 (" + allSlaves.size() + "台):");
for (SlaveGateway slave : allSlaves) {
System.out.println(" • " + slave.getName() +
" (跳数: " + slave.getHopCount() + ")");
}
// ========== 9. 动态拓扑修改 ==========
System.out.println("\n⚡ 动态拓扑修改演示");
System.out.println("========================================");
// 9.1 添加新设备
STADevice newDevice = new STADevice(
"STA-NEW-001", "新测试设备", "10.0.2.150", "00:1B:2C:3D:4E:88",
"Test Device", "v1.0.0"
);
try {
buildingA.addDevice(newDevice);
System.out.println("✅ 成功添加设备: " + newDevice.getName());
} catch (Exception e) {
System.out.println("❌ 添加失败: " + e.getMessage());
}
// 9.2 移除设备
try {
buildingA.removeDevice("STA-PC-001");
System.out.println("✅ 成功移除设备: STA-PC-001");
} catch (Exception e) {
System.out.println("❌ 移除失败: " + e.getMessage());
}
// 9.3 显示更新后的拓扑
System.out.println("\n🔄 更新后的拓扑结构:");
master.display(0);
// ========== 10. 异常处理演示 ==========
System.out.println("\n⚠️ 异常处理演示");
System.out.println("========================================");
try {
// STA设备尝试添加子设备(应该抛出异常)
server1.addDevice(newDevice);
} catch (UnsupportedOperationException e) {
System.out.println("捕获到预期异常: " + e.getMessage());
}
System.out.println("\n✨ 组合模式演示完成!");
}
}
五、组合模式的两种变体
5.1 透明组合模式(本例采用)
java
// 优点:客户端完全透明,无需知道是叶子还是组合节点
// 缺点:叶子节点需要实现不需要的方法(抛出异常)
public abstract class Component {
public abstract void operation();
public void add(Component c) {
throw new UnsupportedOperationException();
}
}
5.2 安全组合模式
java
// 优点:接口安全,叶子节点没有多余方法
// 缺点:客户端需要判断节点类型
public abstract class Component {
public abstract void operation();
}
public class Composite extends Component {
private List<Component> children;
public void add(Component c) { children.add(c); } // 只有组合节点有此方法
}
六、组合模式在网关系统中的优势
6.1 设计优势
| 优势 | 说明 |
|---|---|
| 统一接口 | STA、从网关、主网关使用相同接口 |
| 简化客户端 | 客户端无需区分设备类型 |
| 灵活扩展 | 轻松添加新设备类型 |
| 递归处理 | 自动处理多层级拓扑 |
6.2 实际应用价值
- 设备监控:一键获取全网设备状态
- 拓扑发现:自动构建网络拓扑图
- 批量配置:统一配置所有设备
- 故障定位:快速定位故障点
- 资源统计:准确统计网络资源
七、与其他设计模式的关系
7.1 与迭代器模式
java
// 组合模式 + 迭代器模式 = 强大的遍历能力
public class GatewayIterator implements Iterator<NetworkDevice> {
private Stack<NetworkDevice> stack = new Stack<>();
public GatewayIterator(NetworkDevice root) {
stack.push(root);
}
public boolean hasNext() {
return !stack.isEmpty();
}
public NetworkDevice next() {
NetworkDevice device = stack.pop();
if (device instanceof Gateway) {
List<NetworkDevice> children = ((Gateway) device).getChildren();
for (int i = children.size() - 1; i >= 0; i--) {
stack.push(children.get(i));
}
}
return device;
}
}
7.2 与访问者模式
java
// 组合模式 + 访问者模式 = 分离操作与结构
public interface DeviceVisitor {
void visit(STA device);
void visit(Gateway gateway);
}
public class StatisticsVisitor implements DeviceVisitor {
private int totalDevices = 0;
private int onlineDevices = 0;
public void visit(STA device) {
totalDevices++;
if (device.isOnline()) onlineDevices++;
}
public void visit(Gateway gateway) {
totalDevices++;
if (gateway.isOnline()) onlineDevices++;
// 递归访问子设备
for (NetworkDevice child : gateway.getChildren()) {
if (child instanceof STA) visit((STA) child);
else if (child instanceof Gateway) visit((Gateway) child);
}
}
}
八、最佳实践和注意事项
8.1 何时使用组合模式
- ✅ 需要表示对象的"部分-整体"层次结构
- ✅ 希望客户端忽略组合与单个对象的差异
- ✅ 系统中有树形结构的需求
- ✅ 需要对树中所有节点执行统一操作
8.2 注意事项
- 合理设计接口:避免接口过于庞大
- 性能考虑:深度递归可能影响性能
- 内存管理:注意循环引用问题
- 异常处理:合理处理叶子节点不支持的操作
8.3 扩展思考
java
// 可能的扩展方向:
// 1. 设备分组:支持按功能分组
// 2. 权限控制:不同设备有不同的管理权限
// 3. 事件通知:设备状态变化通知父节点
// 4. 负载均衡:网关自动分配连接
九、总结
组合模式通过将对象组织成树形结构,实现了部分-整体关系的统一处理。在网关拓扑管理系统中:
- 简化了复杂结构:多层级网关关系变得清晰
- 统一了操作接口:无论操作单个设备还是整个网络,接口一致
- 提高了扩展性:新增设备类型无需修改现有代码
- 增强了可维护性:拓扑结构的变化对客户端透明
设计模式的价值 不在于模式的复杂,而在于它提供的设计思想。组合模式教会我们:通过统一接口处理复杂层次结构,可以让系统更灵活、更健壮、更易于维护。
正如计算机科学家克里斯托弗·亚历山大所说:"每个模式都是一个三部分规则,它表达了一个特定上下文、一个问题和一个解决方案之间的关系。" 组合模式就是在"部分-整体"层次结构这个上下文中,解决统一处理问题的优雅方案。