组合模式
本课属于 Coddy C# 学习旅程的“面向对象编程”单元。第 61 课,共 70 课。
组合模式是一种结构型模式,它可以将对象组合成树形结构,并以统一的方式处理单个对象和对象组。它非常适合表示整体与部分的层次结构,让你可以通过同一个接口处理单个项和集合。
该模式有三个关键组成部分:定义通用操作的组件接口、表示单个对象的叶子类,以及包含子对象并实现相同接口的组合类:
public interface IFileSystemItem
{
string Name { get; }
int GetSize();
}
public class File : IFileSystemItem
{
public string Name { get; }
private int _size;
public File(string name, int size)
{
Name = name;
_size = size;
}
public int GetSize() => _size;
}
public class Folder : IFileSystemItem
{
public string Name { get; }
private List<IFileSystemItem> _items = new List<IFileSystemItem>();
public Folder(string name) => Name = name;
public void Add(IFileSystemItem item) => _items.Add(item);
public int GetSize() => _items.Sum(item => item.GetSize());
}这种模式的优点是,客户端不需要知道自己处理的是单个文件还是整个文件夹层级结构:
var root = new Folder("root");
root.Add(new File("doc.txt", 100));
var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));
root.Add(subFolder);
Console.WriteLine(root.GetSize()); // 650组合模式适用于文件系统、UI 组件树、组织结构图,以及任何需要以相同方式处理容器和内容的结构。它通过消除区分简单元素和复杂元素的需要,简化客户端代码。
挑战
简单让我们使用组合模式构建一个组织架构系统。你将创建一种结构,让单个员工和部门(部门中包含员工或其他部门)都能以统一的方式处理,从而计算层级结构中任意级别的总薪资成本。
你将把代码分到以下三个文件中:
OrgComponent.cs:在Organization命名空间中定义IOrgComponent接口,其中包含两个成员:Name属性(字符串)和返回整数的GetSalary()方法。这个接口表示单个员工和部门都要遵循的共同约定。OrgClasses.cs:在同一命名空间中创建两个类:Employee:表示单个员工的叶子类。它在构造函数中接收姓名和薪资,并通过返回自己的薪资来实现该接口。Department:表示一个群组的组合类。它在构造函数中接收名称,并维护一个包含IOrgComponent项目的列表。添加一个Add(IOrgComponent component)方法来添加成员。它的GetSalary()应返回所有成员薪资的总和。
Program.cs:根据输入构建组织结构并计算总薪资。你将创建部门和员工,按适当的层级嵌套它们,并展示组合模式如何让你以统一的方式处理整个层级结构。
你将收到以下输入:
- 要创建的组件数量
- 对于每个组件:类型(
employee或department),后面跟着名称;如果是员工,还会提供薪资。如果是部门,还会提供父部门名称(如果是顶层部门,则为root)
每个组件的输入格式:
employee
{name}
{salary}
{parent_department}
department
{name}
{parent_department}构建结构后,按照 {root_name} Total Salary: {amount} 的格式输出根部门的总薪资。
例如,输入如下:
5
department
Engineering
root
employee
Alice
5000
Engineering
employee
Bob
4500
Engineering
department
QA
Engineering
employee
Charlie
4000
QA输出应为:
Engineering Total Salary: 13500Engineering 部门包含 Alice(5000)、Bob(4500)和 QA 部门。QA 中包含 Charlie(4000)。在 Engineering 上调用 GetSalary() 时,它会递归计算所有嵌套组件的薪资总和,这展示了组合模式如何让你通过简单、统一的接口处理复杂的树形结构!
亲手试试
using System;
using System.Collections.Generic;
using Organization;
class Program
{
public static void Main(string[] args)
{
int n = Convert.ToInt32(Console.ReadLine());
// TODO: 创建一个按名称存储部门的字典
// 这样你在添加成员时就能找到上级部门
Dictionary<string, Department> departments = new Dictionary<string, Department>();
// TODO: 记录根部门
Department root = null;
for (int i = 0; i < n; i++)
{
string type = Console.ReadLine();
if (type == "department")
{
string name = Console.ReadLine();
string parent = Console.ReadLine();
// TODO: 创建部门
// TODO: 将其添加到 departments 字典中
// TODO: 如果 parent 是 "root",这个部门就是根部门
// TODO: 否则,找到上级部门并将这个部门添加到其中
}
else if (type == "employee")
{
string name = Console.ReadLine();
int salary = Convert.ToInt32(Console.ReadLine());
string parent = Console.ReadLine();
// TODO: 创建员工
// TODO: 找到上级部门并将员工添加到其中
}
}
// TODO: 输出根部门的工资总额
// 格式:"{root_name} Total Salary: {amount}"
Console.WriteLine($"{root.Name} Total Salary: {root.GetSalary()}");
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C# 在线编译器