Menu
CoddyTech

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());  // 650

Mẫ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.

challenge icon

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ện IOrgComponent trong không gian tên Organization với hai thành phần: thuộc tính Name (chuỗi) và phương thức GetSalary() 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ức Add(IOrgComponent component) để thêm thành viên. Phương thức GetSalary() 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 (employee hoặc department), 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ặc root nế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: 13500

Phò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()}");
    }
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch C# online