目录
- 一、场景
- 1、题目描述 【[案例来源](https://kamacoder.com/problempage.php?pid=1090)】
- 2、输入描述
- 3、输出描述
- 4、输入示例
- 5、输出示例
- 二、实现(假的组合模式)
- 1、代码
- 2、为什么上面的写法是假的组合模式?
- 三、实现(真的组合模式)
- 1、案例来源的实现
- 2、我的实现
- 四、个人思考
一、场景
设计模式很依赖场景,通过场景也能更好理解这种模式解决了什么问题。
- 无论是学校还是公司,都存在组织结构。而且,这种结构多半是树状结构。
- 当类与类之间的结构也需要表达成树状结构时,组合模式就来大显身手了。
1、题目描述 【案例来源】
小明所在的公司内部有多个部门,每个部门下可能有不同的子部门或者员工。
请你设计一个组合模式来管理这些部门和员工,实现对公司组织结构的统一操作。部门和员工都具有一个通用的接口,可以获取他们的名称以及展示公司组织结构。
2、输入描述
第一行是一个整数 N(1 <= N <= 100),表示后面有 N 行输入。
接下来的 N 行,每行描述一个部门或员工的信息。部门的信息格式为 D 部门名称,员工的信息格式为 E 员工名称,其中 D 或 E 表示部门或员工。
3、输出描述
输出公司的组织结构,展示每个部门下的子部门和员工
4、输入示例
MyCompany
8
D HR
E HRManager
D Finance
E AccountantA
E AccountantB
D IT
E DeveloperA
E DeveloperB
5、输出示例
Company Structure:
MyCompany
HR
HRManager
Finance
AccountantA
AccountantB
IT
DeveloperA
DeveloperB
二、实现(假的组合模式)
1、代码
public interface Component {
String showName();
}
public class Company implements Component {
private final String name;
@Setter
private List<Department> departments;
public Company(String name) {
this.name = name;
}
public void addDepartment(Department department) {
if (CollectionUtils.isEmpty(departments)) {
departments = new ArrayList<>();
departments.add(department);
} else {
departments.add(department);
}
}
@Nullable
public Department gotLastDepartment() {
if (CollectionUtils.isEmpty(departments)) {
return null;
} else {
return departments.get(departments.size() - 1);
}
}
@Override
public String showName() {
return name;
}
public String showCompanyStructure() {
StringBuilder sb = new StringBuilder("Company Structure:\n").append(showName()).append("\n");
Optional.ofNullable(departments).ifPresent(departments -> departments.stream().filter(Objects::nonNull).forEach(department -> {
sb.append(" ").append(department.showName()).append("\n");
Optional.ofNullable(department.getEmployees()).ifPresent(employees -> {
employees.stream().forEach(
employee -> sb.append(" ").append(employee.showName()).append("\n")
);
});
}));
return sb.toString();
}
}
@Data
public class Department implements Component {
private String name;
private List<Employee> employees;
public Department(String name) {
this.name = name;
}
@Override
public String showName() {
return this.name;
}
public void addEmployee(Employee employee) {
if (CollectionUtils.isEmpty(employees)) {
employees = new ArrayList<>();
employees.add(employee);
} else {
employees.add(employee);
}
}
}
@AllArgsConstructor
public class Employee implements Component {
private String name;
@Override
public String showName() {
return name;
}
}
public class Application {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String companyName = scanner.nextLine();
Company company = new Company(companyName);
int n = scanner.nextInt();
for (int i = 0; i < n; i++) {
String type = scanner.next();
String name = scanner.next();
if (StringUtils.equals(type, "D")) {
company.addDepartment(new Department(name));
} else if (StringUtils.equals(type, "E")) {
Department department = company.gotLastDepartment();
department.addEmployee(new Employee(name));
}
}
System.out.printf(company.showCompanyStructure());
}
}
2、为什么上面的写法是假的组合模式?
- 场景中的公司结构:
- 对这个结构进行建模,不应该是:
public class Company implements Component {
private final String name;
@Setter
private List<Department> departments;
}
public class Department implements Component {
private String name;
private List<Employee> employees;
}
public class Employee implements Component {
private String name;
}
- 因为上图实际上有2个基本结构:
// Company-Department (1 对 n)、Department-Employee(1对n)
class Composite implements Component {
private String name;
private List<Component> children;
}
// 这个便是Employee
class Leaf implements Component {
private String name;
}
- 所以,没有Get到这一点,就容易写出假的组合模式。(写的时候也会发现写的很累)
三、实现(真的组合模式)
1、案例来源的实现
- 场景中题目的提供方,也给出了相应的实现:
public interface Component {
void display(int depth);
}
public class Department implements Component {
private String name;
private List<Component> children;
public Department(String name) {
this.name = name;
this.children = new ArrayList<>();
}
public void add(Component component) {
children.add(component);
}
@Override
public void display(int depth) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < depth; i++) {
indent.append(" ");
}
System.out.println(indent + name);
for (Component component : children) {
component.display(depth + 1);
}
}
}
public class Employee implements Component {
private String name;
public Employee(String name) {
this.name = name;
}
@Override
public void display(int depth) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < depth; i++) {
indent.append(" ");
}
System.out.println(indent + " " + name);
}
}
public class Company {
private String name;
private Department root;
public Company(String name) {
this.name = name;
this.root = new Department(name);
}
public void add(Component component) {
root.add(component);
}
public void display() {
System.out.println("Company Structure:");
root.display(0); // 从 1 开始,以适配指定的缩进格式
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取公司名称
String companyName = scanner.nextLine();
Company company = new Company(companyName);
// 读取部⻔和员工数量
int n = scanner.nextInt();
scanner.nextLine(); // 消耗换行符
// 读取部⻔和员工信息
for (int i = 0; i < n; i++) {
String type = scanner.next();
String name = scanner.nextLine().trim();
if ("D".equals(type)) {
Department department = new Department(name);
company.add(department);
} else if ("E".equals(type)) {
Employee employee = new Employee(name);
company.add(employee);
}
}
// 输出公司组织结构
company.display();
}
}
- debug会发现,这并没有正确表示company的结构:
- 那为啥能输出这样的结果呢?
MyCompany
HR
HRManager
Finance
AccountantA
AccountantB
IT
DeveloperA
DeveloperB
- 原因在于:
public class Employee implements Component {
...
@Override
public void display(int depth) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < depth; i++) {
indent.append(" ");
}
System.out.println(indent + " " + name); // 这里的depth实际上是1,因此作者多加了" "
}
}
显然,这并不是正确的实现。
2、我的实现
public interface Component {
void showName(int depth);
}
public class Composite implements Component {
private String name;
private List<Component> children;
public Composite(String name) {
this.name = name;
children = new ArrayList<>();
}
public void addComponent(Component component) {
children.add(component);
}
public Composite gotLastComposite() {
if (!children.isEmpty()) {
Component component = children.get(children.size() - 1);
if (component instanceof Composite) {
return (Composite) component;
} else {
throw new RuntimeException("last component is not composite");
}
} else {
throw new RuntimeException("doesn't exist last component");
}
}
@Override
public void showName(int depth) {
System.out.println(StringUtils.repeat(" ", depth) + name);
for (Component child : children) {
child.showName(depth + 1);
}
}
}
@AllArgsConstructor
public class Employee implements Component {
private String name;
@Override
public void showName(int depth) {
System.out.println(StringUtils.repeat(" ", depth) + name);
}
}
public class Company extends Composite {
public Company(String name) {
super(name);
}
@Override
public void showName(int depth) {
System.out.println("Company Structure:");
super.showName(depth);
}
}
public class Application {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String companyName = scanner.nextLine();
Company company = new Company(companyName);
int n = scanner.nextInt();
for (int i = 0; i < n; i++) {
String type = scanner.next();
String name = scanner.next();
if (StringUtils.equals(type, "D")) {
company.addComponent(new Composite(name));
} else if (StringUtils.equals(type, "E")) {
Composite department = company.gotLastComposite();
department.addComponent(new Employee(name));
}
}
company.showName(0);
}
}
- Company的结构:
四、个人思考
- 对真实场景建模后,类呈现树状结构,那么可以尝试使用组合模式设计代码:
class Composite implements Component {
private String name;
private List<Component> children;
}
// 这个便是Employee
class Leaf implements Component {
private String name;
}
组合模式:实现接口A + 组合多个接口A
- 装饰模式、桥接模式都用到了组合的理念,但都是一对一的形式:
- 装饰模式:实现接口A + 组合接口A
public class EncryptDataSourceImpl implements DataSource {
private DataSource dataSource;
}
- 桥接模式:实现接口A + 组合接口B
public class Circle implements Shape {
private Color color;
}