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 PHP của Coddy. Bài học 80 trên 91.

Mẫu Composite là một mẫu thiết kế cấu trúc, cho phép bạn ghép các đối tượng thành cấu trúc cây rồi làm việc với những cấu trúc này như thể chúng là các đối tượng riêng lẻ. Mẫu này rất phù hợp để biểu diễn hệ thống phân cấp bộ phận và tổng thể, khi bạn muốn xử lý đồng nhất các đối tượng riêng lẻ và nhóm đối tượng.

Hãy xem xét một hệ thống tệp. Một thư mục có thể chứa các tệp và thư mục khác.

Khi tính tổng kích thước, bạn muốn gọi cùng một phương thức cho cả tệp và thư mục, còn thư mục sẽ cộng đệ quy kích thước nội dung bên trong. Mẫu Composite giúp việc này diễn ra liền mạch bằng cách cung cấp cùng một giao diện cho cả nút lá (tệp) và nút tổng hợp (thư mục).

<?php
interface FileSystemItem {
    public function getName(): string;
    public function getSize(): int;
}

class File implements FileSystemItem {
    public function __construct(
        private string $name,
        private int $size
    ) {}
    
    public function getName(): string {
        return $this->name;
    }
    
    public function getSize(): int {
        return $this->size;
    }
}

class Folder implements FileSystemItem {
    private array $items = [];
    
    public function __construct(private string $name) {}
    
    public function getName(): string {
        return $this->name;
    }
    
    public function add(FileSystemItem $item): void {
        $this->items[] = $item;
    }
    
    public function getSize(): int {
        $total = 0;
        foreach ($this->items as $item) {
            $total += $item->getSize();
        }
        return $total;
    }
}

Bây giờ bạn có thể xây dựng cấu trúc cây và xử lý chúng một cách thống nhất:

<?php
$docs = new Folder("Documents");
$docs->add(new File("resume.pdf", 250));
$docs->add(new File("cover.docx", 50));

$images = new Folder("Images");
$images->add(new File("photo.jpg", 1500));

$root = new Folder("Root");
$root->add($docs);
$root->add($images);
$root->add(new File("readme.txt", 10));

echo $root->getSize();

Đầu ra:

1810

Đoạn code phía client gọi getSize() mà không cần quan tâm nó đang xử lý một tệp đơn lẻ hay một cấu trúc thư mục lồng nhau sâu. Mẫu này tự xử lý đệ quy (recursion), giúp bạn dễ dàng làm việc với các hệ phân cấp phức tạp.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống sơ đồ tổ chức bằng Mẫu tổng hợp (Composite Pattern). Các công ty có cấu trúc phân cấp, trong đó phòng ban có thể chứa nhân viên và các phòng ban nhỏ hơn: đây là cấu trúc cây hoàn hảo để bạn tính tổng đồng nhất trên toàn tổ chức.

Bạn sẽ sắp xếp code trong bốn tệp:

  • OrganizationUnit.php: Định nghĩa giao diện OrganizationUnit mà cả nhân viên riêng lẻ lẫn phòng ban đều triển khai. Giao diện này cần có hai phương thức: getName(): string và getSalary(): int. Giao diện chung này cho phép bạn xử lý nhân viên riêng lẻ và toàn bộ phòng ban theo cùng một cách khi tính chi phí.
  • Employee.php: Đưa giao diện OrganizationUnit vào và tạo lớp Employee triển khai giao diện đó. Nhân viên là nút lá trong cây tổ chức. Họ có tên và mức lương nhưng không thể chứa các đơn vị khác. Dùng cú pháp rút gọn hàm khởi tạo để nhận tên và mức lương, rồi triển khai cả hai phương thức của giao diện.
  • Department.php: Đưa giao diện OrganizationUnit vào và tạo lớp Department triển khai giao diện đó. Phòng ban là một đối tượng tổng hợp có thể chứa cả nhân viên lẫn các phòng ban khác. Phòng ban của bạn cần:
    • Nhận tên trong hàm khởi tạo
    • Triển khai getName() để trả về tên phòng ban
    • Có phương thức add(OrganizationUnit $unit): void để thêm nhân viên hoặc phòng ban nhỏ hơn
    • Triển khai getSalary() để trả về tổng mức lương của tất cả đơn vị mà phòng ban chứa, bằng cách cộng đệ quy mọi mức lương bên trong
  • main.php: Đưa các tệp Employee và Department vào. Bạn sẽ nhận một đầu vào: một chuỗi JSON biểu diễn cấu trúc tổ chức.

    JSON có định dạng sau:

    {"Engineering": [{"name": "Alice", "salary": 80000}, {"name": "Bob", "salary": 75000}], "Sales": [{"name": "Carol", "salary": 65000}]}

    Mỗi khóa là tên một phòng ban, còn giá trị của nó là một mảng nhân viên cùng tên và mức lương của họ.

    Tạo một Department gốc có tên "Company". Với mỗi phòng ban trong JSON, hãy tạo một Department, thêm nhân viên vào đó, rồi thêm phòng ban ấy vào Company. Cuối cùng, in ra tổng mức lương của công ty theo định dạng sau:

    Total Salary: [amount]

Điểm hay của Mẫu tổng hợp là khi gọi getSalary() trên phòng ban Company, phương thức này tự động tính tổng mức lương của tất cả phòng ban và nhân viên lồng bên trong. Code phía gọi không cần biết cấu trúc nội bộ.

Tự làm thử

<?php
require_once 'Employee.php';
require_once 'Department.php';

// Đọc đầu vào JSON
$input = trim(fgets(STDIN));
$data = (array)json_decode($input, true);

// TODO: Tạo Department gốc có tên "Company"

// TODO: Lặp qua từng phòng ban trong dữ liệu JSON
// Với mỗi phòng ban:
//   - Tạo một đối tượng Department mới
//   - Thêm từng nhân viên vào phòng ban đó
//   - Thêm phòng ban vào Company

// TODO: In tổng tiền lương theo định dạng: Total Salary: [amount]

?>
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 PHP online