组合模式
本课属于 Coddy Java 学习旅程的“面向对象编程”单元。第 75 课,共 87 课。
组合模式是一种结构型设计模式,可将对象组合成树形结构,以表示部分与整体的层次关系。它通过统一的接口,让客户端能够以相同的方式处理单个对象和对象组合。
想一想文件系统:文件夹可以包含文件和其他文件夹,而这些文件夹又可以包含更多文件和文件夹。无论你处理的是单个文件还是整个文件夹结构,都希望以相同的方式执行“获取大小”等操作。这个模式定义了 Component 接口、Leaf 对象(单个元素)和 Composite 对象(包含子对象的容器):
interface FileComponent {
void display(String indent);
int getSize();
}
class File implements FileComponent {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public void display(String indent) {
System.out.println(indent + name + " (" + size + "KB)");
}
public int getSize() {
return size;
}
}
class Folder implements FileComponent {
private String name;
private List<FileComponent> children = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void add(FileComponent component) {
children.add(component);
}
public void display(String indent) {
System.out.println(indent + name + "/");
for (FileComponent child : children) {
child.display(indent + " ");
}
}
public int getSize() {
return children.stream().mapToInt(FileComponent::getSize).sum();
}
}组合对象将操作委托给其子对象,从而支持递归结构。客户端可以与树交互,而无需知道自己处理的是叶节点还是组合对象:
Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));
Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);
root.display("");
System.out.println("Total: " + root.getSize() + "KB");组合模式非常适合表示组织结构图、UI 组件或菜单系统等层次结构,这些场景中你需要以相同的方式处理群组和个体。
挑战
简单让我们使用组合模式构建一个组织架构系统!你将创建一种结构,其中部门可以包含员工和其他子部门,从而统一计算总薪资并展示层级结构,无论你查看的是单个员工还是整个部门。
你将把代码分布在 4 个文件中:
OrganizationComponent.java:定义组件接口,员工和部门都将实现该接口。接口应声明两个方法:showDetails(String indent),用于以合适的缩进显示组件;以及getSalary(),用于以整数形式返回总薪资。Employee.java:创建表示单个员工的叶子类。Employee有姓名(String)和薪资(int),两者都通过构造函数设置。调用showDetails时,应打印[indent][name]: $[salary]。getSalary方法只需返回员工的薪资。Department.java:创建可以容纳员工和子部门的组合类。Department有一个名称(String),并维护一个由OrganizationComponent子项组成的列表。它应提供add(OrganizationComponent component)方法来添加成员。调用showDetails时,打印[indent][name] Department,然后对每个子项调用showDetails,并增加缩进(添加两个空格)。getSalary方法返回所有子项薪资的总和。Main.java:构建你的组织!你将收到 4 项输入:两名员工的姓名和薪资,以及一个子部门的名称和一名员工的信息。创建一个名为 "Engineering" 的部门作为根部门。使用前两组姓名和薪资创建两名员工,并将他们添加到该部门。然后,使用第三项输入创建一个子部门,使用第四组姓名和薪资创建一名员工并添加到该子部门,最后将这个子部门添加到 Engineering。
在 Engineering 部门上调用
showDetails(""),然后按Total Salary: $[amount]的格式打印总薪资。
你将按以下顺序收到输入:employee1 的姓名(String)、employee1 的薪资(int)、employee2 的姓名(String)、employee2 的薪资(int)、子部门名称(String)、employee3 的姓名(String)、employee3 的薪资(int)。
例如,输入为 Alice、75000、Bob、65000、QA、Charlie、55000 时,输出如下:
Engineering Department
Alice: $75000
Bob: $65000
QA Department
Charlie: $55000
Total Salary: $195000注意,无论是在单个员工还是在包含嵌套子部门的整个部门上调用,相同的 showDetails 和 getSalary 方法都能顺畅工作。组合结构会自动处理递归,让你可以统一处理整个层级结构!
亲手试试
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取输入
String emp1Name = scanner.nextLine();
int emp1Salary = Integer.parseInt(scanner.nextLine());
String emp2Name = scanner.nextLine();
int emp2Salary = Integer.parseInt(scanner.nextLine());
String subDeptName = scanner.nextLine();
String emp3Name = scanner.nextLine();
int emp3Salary = Integer.parseInt(scanner.nextLine());
// TODO:创建 "Engineering" 部门作为根节点
// TODO:使用 emp1 和 emp2 的数据创建两名员工,并将他们添加到 Engineering 部门
// TODO:使用 subDeptName 创建子部门
// TODO:使用 emp3 的数据创建一名员工,并将其添加到子部门
// TODO:将子部门添加到 Engineering 部门
// TODO:对 Engineering 部门调用 showDetails("")
// TODO:按以下格式输出薪资总额:Total Salary: $[amount]
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Java 在线编译器