深入理解设计模式之组合模式
1. 组合模式概述
组合模式(Composite Pattern)是一种结构型设计模式,它允许将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得客户端可以统一对待单个对象和组合对象,无需区分它们的差异。
这种模式创建了一个包含自身对象组的类,允许客户端以相同的方式处理单个对象和组合对象,简化了复杂树形结构的处理。
2. 组合模式的核心组成
- 组件(Component): 为所有对象声明接口,管理子组件的接口
- 叶子(Leaf): 表示组合中的叶子节点对象,没有子节点
- 组合(Composite): 存储子组件并实现子组件相关操作
3. 组合模式基本实现
// 组件接口
interface Component {
void operation();
void add(Component component);
void remove(Component component);
Component getChild(int index);
}
// 叶子节点
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("Leaf " + name + " operation");
}
@Override
public void add(Component component) {
System.out.println("Cannot add to a leaf");
}
@Override
public void remove(Component component) {
System.out.println("Cannot remove from a leaf");
}
@Override
public Component getChild(int index) {
System.out.println("Cannot get child from a leaf");
return null;
}
}
// 组合节点
class Composite implements Component {
private List<Component> children = new ArrayList<>();
private String name;
public Composite(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("Composite " + name + " operation");
// 对所有子组件进行操作
for (Component component : children) {
component.operation();
}
}
@Override
public void add(Component component) {
children.add(component);
}
@Override
public void remove(Component component) {
children.remove(component);
}
@Override
public Component getChild(int index) {
if (index < 0 || index >= children.size()) {
return null;
}
return children.get(index);
}
}
// 客户端代码
public class BasicCompositeDemo {
public static void main(String[] args) {
// 创建树形结构
Composite root = new Composite("Root");
Composite branch1 = new Composite("Branch 1");
Composite branch2 = new Composite("Branch 2");
Leaf leaf1 = new Leaf("Leaf 1");
Leaf leaf2 = new Leaf("Leaf 2");
Leaf leaf3 = new Leaf("Leaf 3");
// 组装树形结构
root.add(branch1);
root.add(branch2);
branch1.add(leaf1);
branch1.add(leaf2);
branch2.add(leaf3);
// 操作整个树
root.operation();
}
}
4. 实际应用场景
4.1 文件系统实现
// 文件系统组件
abstract class FileSystemComponent {
protected String name;
protected String path;
public FileSystemComponent(String name, String path) {
this.name = name;
this.path = path;
}
public String getName() {
return name;
}
public String getPath() {
return path;
}
public abstract long getSize();
public abstract void display(int indent);
// 辅助方法,生成缩进
protected String getIndent(int indent) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indent; i++) {
sb.append(" ");
}
return sb.toString();
}
}
// 文件
class File extends FileSystemComponent {
private long size;
public File(String name, String path, long size) {
super(name, path);
this.size = size;
}
@Override
public long getSize() {
return size;
}
@Override
public void display(int indent) {
System.out.println(getIndent(indent) + "File: " + name + " (" + size + " bytes)");
}
}
// 目录
class Directory extends FileSystemComponent {
private List<FileSystemComponent> children = new ArrayList<>();
public Directory(String name, String path) {
super(name, path);
}
public void addComponent(FileSystemComponent component) {
children.add(component);
}
public void removeComponent(FileSystemComponent component) {
children.remove(component);
}
public FileSystemComponent getChild(int index) {
if (index >= 0 && index < children.size()) {
return children.get(index);
}
return null;
}
public List<FileSystemComponent> getChildren() {
return new ArrayList<>(children);
}
@Override
public long getSize() {
long totalSize = 0;
for (FileSystemComponent component : children) {
totalSize += component.getSize();
}
return totalSize;
}
@Override
public void display(int indent) {
System.out.println(getIndent(indent) + "Directory: " + name + " (" + getSize() + " bytes)");
for (FileSystemComponent component : children) {
component.display(indent + 1);
}
}
}
// 文件系统演示
public class FileSystemDemo {
public static void main(String[] args) {
// 创建文件系统结构
Directory root = new Directory("root", "/");
Directory home = new Directory("home", "/home");
Directory user = new Directory("user", "/home/user");
File file1 = new File("document.txt", "/home/user/document.txt", 2048);
File file2 = new File("image.jpg", "/home/user/image.jpg", 4096);
File file3 = new File("config.xml", "/config.xml", 1024);
// 组装文件系统
user.addComponent(file1);
user.addComponent(file2);
home.addComponent(user);
root.addComponent(home);
root.addComponent(file3);
// 显示文件系统
root.display(0);
// 计算特定目录大小
System.out.println("\nSize of /home/user: " + user.getSize() + " bytes");
}
}
4.2 组织结构实现
// 组织组件
abstract class OrganizationComponent {
protected String name;
protected String description;
public OrganizationComponent(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public abstract void display(int depth);
public abstract int getEmployeeCount();
protected String getIndent(int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append(" ");
}
return sb.toString();
}
}
// 员工
class Employee extends OrganizationComponent {
private String position;
private double salary;
public Employee(String name, String description, String position, double salary) {
super(name, description);
this.position = position;
this.salary = salary;
}
public String getPosition() {
return position;
}
public double getSalary() {
return salary;
}
@Override
public void display(int depth) {
System.out.println(getIndent(depth) + "Employee: " + name + " - " + position);
}
@Override
public int getEmployeeCount() {
return 1;
}
}
// 部门
class Department extends OrganizationComponent {
private List<OrganizationComponent> subordinates = new ArrayList<>();
public Department(String name, String description) {
super(name, description);
}
public void add(OrganizationComponent component) {
subordinates.add(component);
}
public void remove(OrganizationComponent component) {
subordinates.remove(component);
}
@Override
public void display(int depth) {
System.out.println(getIndent(depth) + "Department: " + name + " (" + getEmployeeCount() + " employees)");
for (OrganizationComponent component : subordinates) {
component.display(depth + 1);
}
}
@Override
public int getEmployeeCount() {
int count = 0;
for (OrganizationComponent component : subordinates) {
count += component.getEmployeeCount();
}
return count;
}
}
// 组织结构演示
public class OrganizationDemo {
public static void main(String[] args) {
// 创建组织结构
Department company = new Department("ABC Corp", "A technology company");
Department engineering = new Department("Engineering", "Develops products");
Department sales = new Department("Sales", "Sells products");
Department development = new Department("Development", "Writes code");
Department testing = new Department("Testing", "Tests code");
Employee ceo = new Employee("John CEO", "Chief Executive Officer", "CEO", 150000);
Employee vpEng = new Employee("Mike VP", "VP of Engineering", "VP", 120000);
Employee vpSales = new Employee("Lisa VP", "VP of Sales", "VP", 120000);
Employee dev1 = new Employee("Alice Dev", "Developer", "Software Engineer", 90000);
Employee dev2 = new Employee("Bob Dev", "Developer", "Software Engineer", 85000);
Employee tester = new Employee("Charlie Test", "Tester", "QA Engineer", 75000);
Employee salesRep = new Employee("David Sales", "Sales Representative", "Sales", 80000);
// 组装组织结构
company.add(ceo);
company.add(engineering);
company.add(sales);
engineering.add(vpEng);
engineering.add(development);
engineering.add(testing);
development.add(dev1);
development.add(dev2);
testing.add(tester);
sales.add(vpSales);
sales.add(salesRep);
// 显示组织结构
company.display(0);
// 统计信息
System.out.println("\nTotal employee count: " + company.getEmployeeCount());
System.out.println("Engineering employee count: " + engineering.getEmployeeCount());
}
}
4.3 GUI组件实现
import java.util.ArrayList;
import java.util.List;
// GUI组件接口
interface UIComponent {
void render();
void resize();
String getDescription();
void add(UIComponent component);
void remove(UIComponent component);
UIComponent getChild(int index);
}
// 简单组件 - 按钮
class Button implements UIComponent {
private String text;
public Button(String text) {
this.text = text;
}
@Override
public void render() {
System.out.println("Rendering Button: " + text);
}
@Override
public void resize() {
System.out.println("Resizing Button: " + text);
}
@Override
public String getDescription() {
return "Button: " + text;
}
@Override
public void add(UIComponent component) {
throw new UnsupportedOperationException("Cannot add component to a Button");
}
@Override
public void remove(UIComponent component) {
throw new UnsupportedOperationException("Cannot remove component from a Button");
}
@Override
public UIComponent getChild(int index) {
throw new UnsupportedOperationException("Cannot get child from a Button");
}
}
// 简单组件 - 文本框
class TextField implements UIComponent {
private String value;
public TextField(String value) {
this.value = value;
}
@Override
public void render() {
System.out.println("Rendering TextField: " + value);
}
@Override
public void resize() {
System.out.println("Resizing TextField: " + value);
}
@Override
public String getDescription() {
return "TextField: " + value;
}
@Override
public void add(UIComponent component) {
throw new UnsupportedOperationException("Cannot add component to a TextField");
}
@Override
public void remove(UIComponent component) {
throw new UnsupportedOperationException("Cannot remove component from a TextField");
}
@Override
public UIComponent getChild(int index) {
throw new UnsupportedOperationException("Cannot get child from a TextField");
}
}
// 复合组件 - 面板
class Panel implements UIComponent {
private List<UIComponent> components = new ArrayList<>();
private String name;
public Panel(String name) {
this.name = name;
}
@Override
public void render() {
System.out.println("Rendering Panel: " + name);
for (UIComponent component : components) {
component.render();
}
}
@Override
public void resize() {
System.out.println("Resizing Panel: " + name);
for (UIComponent component : components) {
component.resize();
}
}
@Override
public String getDescription() {
return "Panel: " + name;
}
@Override
public void add(UIComponent component) {
components.add(component);
}
@Override
public void remove(UIComponent component) {
components.remove(component);
}
@Override
public UIComponent getChild(int index) {
if (index >= 0 && index < components.size()) {
return components.get(index);
}
return null;
}
}
// 复合组件 - 窗口
class Window implements UIComponent {
private List<UIComponent> components = new ArrayList<>();
private String title;
public Window(String title) {
this.title = title;
}
@Override
public void render() {
System.out.println("Rendering Window: " + title);
for (UIComponent component : components) {
component.render();
}
}
@Override
public void resize() {
System.out.println("Resizing Window: " + title);
for (UIComponent component : components) {
component.resize();
}
}
@Override
public String getDescription() {
return "Window: " + title;
}
@Override
public void add(UIComponent component) {
components.add(component);
}
@Override
public void remove(UIComponent component) {
components.remove(component);
}
@Override
public UIComponent getChild(int index) {
if (index >= 0 && index < components.size()) {
return components.get(index);
}
return null;
}
}
// GUI组件演示
public class GUICompositeDemo {
public static void main(String[] args) {
// 创建GUI组件
Window mainWindow = new Window("Main Application Window");
Panel topPanel = new Panel("Top Panel");
Panel contentPanel = new Panel("Content Panel");
Panel bottomPanel = new Panel("Bottom Panel");
Button okButton = new Button("OK");
Button cancelButton = new Button("Cancel");
TextField nameField = new TextField("John Doe");
TextField emailField = new TextField("john@example.com");
// 组装GUI组件
bottomPanel.add(okButton);
bottomPanel.add(cancelButton);
contentPanel.add(nameField);
contentPanel.add(emailField);
mainWindow.add(topPanel);
mainWindow.add(contentPanel);
mainWindow.add(bottomPanel);
// 渲染窗口和所有子组件
mainWindow.render();
System.out.println("\n--- Resizing window ---");
mainWindow.resize();
}
}
5. 透明组合模式与安全组合模式
5.1 透明组合模式
透明组合模式是上面示例中使用的方式,它在Component接口中定义了管理子组件的方法。这样客户端可以统一对待所有对象,但缺点是叶子节点必须实现这些方法。
5.2 安全组合模式
安全组合模式只在Composite类中定义管理子组件的方法,这样叶子节点就不需要实现这些方法,但客户端必须知道对象的具体类型。
// 组件接口 - 只定义共同行为
interface Component {
void operation();
}
// 叶子节点
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("Leaf " + name + " operation");
}
}
// 组合节点 - 增加了管理子组件的方法
class Composite implements Component {
private List<Component> children = new ArrayList<>();
private String name;
public Composite(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("Composite " + name + " operation");
for (Component component : children) {
component.operation();
}
}
// 子组件管理方法,仅在Composite中定义
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
public Component getChild(int index) {
if (index < 0 || index >= children.size()) {
return null;
}
return children.get(index);
}
}
// 安全组合模式演示
public class SafeCompositeDemo {
public static void main(String[] args) {
// 创建组合结构
Leaf leaf1 = new Leaf("Leaf 1");
Leaf leaf2 = new Leaf("Leaf 2");
Leaf leaf3 = new Leaf("Leaf 3");
Composite composite1 = new Composite("Composite 1");
Composite composite2 = new Composite("Composite 2");
Composite root = new Composite("Root");
// 组装结构 - 注意必须知道对象是Composite类型
composite1.add(leaf1);
composite1.add(leaf2);
composite2.add(leaf3);
root.add(composite1);
root.add(composite2);
// 操作整个结构
root.operation();
}
}
6. 组合模式的优缺点
优点
- 简化客户端代码:客户端可以一致地处理简单和复杂元素
- 更容易添加新类型的组件:符合开闭原则
- 使得设计更加一般化:容易构建复杂的树形结构
- 符合单一职责原则:将管理子节点的方法集中在一个类中
缺点
- 难以限制组件的类型:通用设计下,可能难以限制组合只能包含特定类型的组件
- 可能使设计过于一般化:有时候很难区分叶子和组合节点
- 可能需要为某些操作创建额外方法:例如查找特定的子组件
7. 组合模式的使用时机
适合使用组合模式的场景:
- 表示对象的部分-整体层次结构
- 希望用户忽略单个对象和组合对象的差异
- 需要统一处理复杂对象和简单对象
- 需要递归遍历树形结构
8. Java标准库中的组合模式
Java标准库中有多处使用了组合模式:
import java.awt.*;
import java.io.File;
import java.util.*;
import javax.swing.*;
public class JavaCompositeExamples {
public static void main(String[] args) {
// 例1: Swing GUI组件
JFrame frame = new JFrame("Composite Example");
JPanel panel = new JPanel();
panel.add(new JButton("Button 1"));
panel.add(new JTextField(20));
panel.add(new JCheckBox("Check me"));
frame.add(panel);
frame.setSize(300, 200);
frame.setVisible(true);
// 例2: AWT容器和组件
Frame awtFrame = new Frame("AWT Example");
Panel awtPanel = new Panel();
awtPanel.add(new Button("Click me"));
awtPanel.add(new TextField(20));
awtFrame.add(awtPanel);
awtFrame.setSize(300, 200);
awtFrame.setVisible(true);
// 例3: 文件系统
File directory = new File("C:/");
if (directory.isDirectory()) {
System.out.println("Directory: " + directory.getName());
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
System.out.println(" [DIR] " + file.getName());
} else {
System.out.println(" [FILE] " + file.getName() + " (" + file.length() + " bytes)");
}
}
}
}
}
}
9. 总结
组合模式是一种强大的结构型设计模式,它通过创建树形结构使得客户端可以统一处理单个对象和组合对象。这种模式在处理具有层次结构的对象集合时特别有用,比如文件系统、GUI组件、组织结构等。
组合模式的关键在于建立组件接口,然后通过叶子节点和组合节点的实现来创建树形结构。客户端通过操作组件接口,无需关心处理的是单个对象还是组合对象,从而简化了代码并提高了灵活性。
在实际应用中,可以根据需求选择透明组合模式或安全组合模式。无论选择哪种方式,组合模式都能有效地处理部分-整体层次结构,是处理树形结构的优雅解决方案。