Mẫu tổ hợp
Thuộc phần Lập trình hướng đối tượng trong hành trình C# của Coddy. Bài học 61 trên 70.
Mẫu Composite là một mẫu cấu trúc cho phép bạn kết hợp các đối tượng thành cấu trúc dạng cây và xử lý thống nhất các đối tượng riêng lẻ lẫn nhóm đối tượng. Mẫu này rất phù hợp để biểu diễn hệ thống phân cấp toàn thể và bộ phận khi bạn muốn làm việc với cả từng mục riêng lẻ và các tập hợp thông qua cùng một giao diện.
Mẫu này có ba thành phần chính: một giao diện thành phần định nghĩa các thao tác chung, các lớp lá đại diện cho từng đối tượng và các lớp tổng hợp chứa các thành phần con và triển khai cùng một giao diện:
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());
}Điểm hay của mẫu này là phía client không cần biết mình đang làm việc với một tệp đơn lẻ hay cả một hệ thống thư mục phân cấp:
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()); // 650Mẫu Composite phù hợp với hệ thống tệp, cây thành phần giao diện người dùng, sơ đồ tổ chức hoặc bất kỳ cấu trúc nào mà vùng chứa và nội dung cần được xử lý theo cùng một cách. Mẫu này giúp đơn giản hóa code phía máy khách bằng cách loại bỏ nhu cầu phân biệt giữa các phần tử đơn giản và phức tạp.
Thử thách
DễHãy xây dựng hệ thống sơ đồ tổ chức bằng mẫu Composite. Bạn sẽ tạo một cấu trúc trong đó nhân viên riêng lẻ và các phòng ban (chứa nhân viên hoặc phòng ban khác) đều có thể được xử lý thống nhất, giúp bạn tính tổng chi phí tiền lương ở bất kỳ cấp nào trong hệ thống phân cấp.
Bạn sẽ sắp xếp code trong ba tệp:
OrgComponent.cs: Định nghĩa giao diệnIOrgComponenttrong không gian tênOrganizationvới hai thành phần: thuộc tínhName(chuỗi) và phương thứcGetSalary()trả về một số nguyên. Giao diện này đại diện cho hợp đồng chung mà cả nhân viên riêng lẻ lẫn phòng ban đều tuân theo.OrgClasses.cs: Tạo hai lớp trong cùng không gian tên:Employee- lớp lá đại diện cho một nhân viên. Lớp này nhận tên và mức lương trong hàm khởi tạo, đồng thời triển khai giao diện bằng cách trả về mức lương của chính nhân viên đó.Department- lớp tổng hợp đại diện cho một nhóm. Lớp này nhận tên trong hàm khởi tạo và duy trì danh sách các phần tửIOrgComponent. Thêm phương thứcAdd(IOrgComponent component)để thêm thành viên. Phương thứcGetSalary()sẽ trả về tổng mức lương của tất cả thành viên.
Program.cs: Xây dựng cấu trúc tổ chức dựa trên đầu vào và tính tổng tiền lương. Bạn sẽ tạo các phòng ban và nhân viên, lồng chúng vào nhau phù hợp, đồng thời minh họa cách mẫu Composite cho phép xử lý thống nhất toàn bộ hệ thống phân cấp.
Bạn sẽ nhận các đầu vào sau:
- Số lượng thành phần cần tạo
- Với mỗi thành phần: loại (
employeehoặcdepartment), tiếp theo là tên, và với nhân viên thì có thêm mức lương. Với phòng ban, bạn cũng sẽ nhận tên phòng ban cha (hoặcrootnếu đó là phòng ban cấp cao nhất)
Định dạng đầu vào cho mỗi thành phần:
employee
{name}
{salary}
{parent_department}
department
{name}
{parent_department}Sau khi xây dựng cấu trúc, hãy in tổng tiền lương của phòng ban gốc theo định dạng {root_name} Total Salary: {amount}.
Ví dụ, nếu đầu vào là:
5
department
Engineering
root
employee
Alice
5000
Engineering
employee
Bob
4500
Engineering
department
QA
Engineering
employee
Charlie
4000
QAĐầu ra sẽ là:
Engineering Total Salary: 13500Phòng ban Engineering gồm Alice (5000), Bob (4500) và phòng ban QA. QA gồm Charlie (4000). Khi gọi GetSalary() trên Engineering, phương thức sẽ tính đệ quy tổng từ tất cả thành phần lồng bên trong, qua đó minh họa cách mẫu Composite cho phép bạn làm việc với cấu trúc cây phức tạp thông qua một giao diện đơn giản, thống nhất!
Tự làm thử
using System;
using System.Collections.Generic;
using Organization;
class Program
{
public static void Main(string[] args)
{
int n = Convert.ToInt32(Console.ReadLine());
// TODO: Tạo từ điển (dictionary) để lưu các phòng ban theo tên
// Từ điển này giúp bạn tìm phòng ban cấp trên khi thêm các thành phần
Dictionary<string, Department> departments = new Dictionary<string, Department>();
// TODO: Theo dõi phòng ban gốc
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: Tạo phòng ban
// TODO: Thêm phòng ban vào từ điển departments
// TODO: Nếu parent là "root", đây là phòng ban gốc
// TODO: Nếu không, tìm phòng ban cấp trên và thêm phòng ban này vào đó
}
else if (type == "employee")
{
string name = Console.ReadLine();
int salary = Convert.ToInt32(Console.ReadLine());
string parent = Console.ReadLine();
// TODO: Tạo nhân viên
// TODO: Tìm phòng ban cấp trên và thêm nhân viên vào đó
}
}
// TODO: In ra tổng lương của phòng ban gốc
// Định dạng: "{root_name} Total Salary: {amount}"
Console.WriteLine($"{root.Name} Total Salary: {root.GetSalary()}");
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Các khái niệm cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiKhông gian tên và chỉ thịGiới thiệu về lớp và đối tượngTừ khóa thisPhương thức và tham sốTrường và thuộc tínhHàm khởi tạoKhởi tạo đối tượngÔn tập: Máy tính đơn giản4Kế thừa
Cú pháp kế thừa cơ bản (:)Từ khóa 'base'Từ khóa virtual và overrideLớp sealedLớp cơ sở 'object'Ôn tập: hệ phân cấp nhân viên7Tính năng nâng cao
Nạp chồng toán tửBộ lập chỉ mục (this[])Ghi đè ToString()Phương thức mở rộngÔn tập: Danh sách tùy chỉnh10Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếSingleton an toàn với luồngMẫu FactoryMẫu Observer (sự kiện)Mẫu Strategy2Thuộc tính và thành viên tĩnh
Thuộc tính tự độngThuộc tính chỉ đọc hoặc chỉ ghiTrường và phương thức tĩnhLớp tĩnhThành viên có thân biểu thức5Đa hình và interface
Đa hình lúc biên dịch và lúc chạyInterface và lớp trừu tượngNhiều interfaceInterface tường minhUpcasting và downcastingÔn tập: bộ tính toán hình học8Các khái niệm OOP nâng cao
Ưu tiên composition hơn kế thừaGenerics (lớp và phương thức)Delegate và sự kiệnAttribute và reflectionGiao diện IDisposable và câu lệnh usingKiến thức cơ bản về dependency injection11Các mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổ hợpTự luyện tập: Trình biên dịch C# online