Menu
CoddyTech

Mẫu trang trí

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 77 trên 91.

Mẫu Decorator là một mẫu thiết kế cấu trúc, cho phép bạn thêm hành vi mới vào đối tượng một cách linh hoạt bằng cách bọc chúng trong các đối tượng bao bọc đặc biệt gọi là decorator. Khác với kế thừa, vốn thêm hành vi tại thời điểm biên dịch, decorator thêm hành vi khi chạy chương trình.

Hãy tưởng tượng một quán cà phê. Bạn bắt đầu với một ly cà phê cơ bản, sau đó thêm các món như sữa, đường hoặc kem tươi.

Mỗi phần bổ sung bọc quanh cà phê ban đầu và thêm mô tả cùng chi phí cho cà phê. Điểm mấu chốt là cả cà phê cơ bản lẫn các lớp trang trí đều dùng chung một giao diện.

<?php
interface Coffee {
    public function getDescription(): string;
    public function getCost(): float;
}

class SimpleCoffee implements Coffee {
    public function getDescription(): string {
        return "Simple Coffee";
    }
    
    public function getCost(): float {
        return 2.00;
    }
}

Decorator triển khai cùng một giao diện và bọc một đối tượng Coffee khác:

<?php
class MilkDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Milk";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.50;
    }
}

class SugarDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Sugar";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.25;
    }
}

$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);

echo $coffee->getDescription() . " = $" . $coffee->getCost();

Đầu ra:

Simple Coffee, Milk, Sugar = $2.75

Mỗi decorator bọc đối tượng trước đó, tạo thành một chuỗi. Mẫu này cho phép bạn kết hợp các hành vi theo bất kỳ thứ tự và số lượng nào mà không cần tạo một lớp cho mọi tổ hợp có thể. Muốn thêm hai phần sữa? Chỉ cần bọc hai lần.

challenge icon

Thử thách

Dễ

Hãy xây dựng hệ thống đặt pizza bằng mẫu Decorator (Decorator Pattern). Bạn sẽ bắt đầu với một chiếc pizza cơ bản rồi thêm các món phủ một cách linh hoạt: mỗi món phủ bọc chiếc pizza và bổ sung vào phần mô tả cũng như giá tiền, giống như ví dụ về cà phê trong bài học.

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

  • Pizza.php: Định nghĩa giao diện Pizza để đặt ra quy ước cho tất cả pizza và món phủ. Giao diện này cần có hai phương thức: getDescription(): string và getPrice(): float.
  • BasePizza.php: Nạp giao diện Pizza và tạo lớp MargheritaPizza triển khai giao diện đó. Đây là pizza cơ bản, có phần mô tả là "Margherita" và giá là 8.00.
  • Toppings.php: Nạp giao diện Pizza và tạo ba lớp trang trí triển khai giao diện đó. Mỗi lớp trang trí bọc một đối tượng Pizza (nhận đối tượng đó trong hàm khởi tạo) và bổ sung vào cả phần mô tả lẫn giá tiền:
    • CheeseTopping: Thêm ", Extra Cheese" vào phần mô tả và 1.50 vào giá tiền
    • PepperoniTopping: Thêm ", Pepperoni" vào phần mô tả và 2.00 vào giá tiền
    • MushroomTopping: Thêm ", Mushrooms" vào phần mô tả và 1.25 vào giá tiền
  • main.php: Nạp các tệp BasePizza và Toppings. Bạn sẽ nhận một đầu vào: danh sách món phủ được phân tách bằng dấu phẩy để thêm vào, ví dụ "cheese,pepperoni" hoặc "mushroom,cheese,cheese".

    Bắt đầu với một MargheritaPizza. Với mỗi món phủ trong đầu vào, hãy bọc pizza bằng lớp trang trí phù hợp:

    • "cheese" được bọc bằng CheeseTopping
    • "pepperoni" được bọc bằng PepperoniTopping
    • "mushroom" được bọc bằng MushroomTopping

    Sau khi thêm tất cả món phủ, hãy in phần mô tả và giá của pizza hoàn chỉnh theo định dạng này:

    [description] = $[price]

    Định dạng giá tiền với hai chữ số thập phân.

Lưu ý rằng bạn có thể thêm cùng một món phủ nhiều lần: gọi món thêm phô mai kép chỉ đơn giản là bọc bằng CheeseTopping hai lần. Đó chính là sức mạnh linh hoạt của mẫu Decorator!

Tự làm thử

<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';

// Đọc đầu vào: danh sách topping được phân tách bằng dấu phẩy
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);

// Bắt đầu với MargheritaPizza cơ bản
$pizza = new MargheritaPizza();

// TODO: Duyệt qua từng topping và bọc pizza bằng decorator phù hợp
// - "cheese" -> bọc bằng CheeseTopping
// - "pepperoni" -> bọc bằng PepperoniTopping
// - "mushroom" -> bọc bằng MushroomTopping

// TODO: In kết quả theo định dạng: [description] = $[price]
// Định dạng giá với 2 chữ số thập phân bằng number_format()

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