Menu
CoddyTech

Mẫu trang trí

Thuộc phần Lập trình hướng đối tượng trong hành trình Python của Coddy. Bài học 51 trên 64.

Mẫu Decorator bổ sung chức năng mới cho các đối tượng một cách linh hoạt mà không thay đổi cấu trúc của chúng. Mẫu này bao bọc các đối tượng để mở rộng hành vi của chúng.

Đây là một ví dụ đơn giản về cà phê:

class Coffee:
    def cost(self):
        return 5
    
    def description(self):
        return "Simple coffee"

Tạo các decorator bổ sung tính năng cho cà phê:

class MilkDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 2
    
    def description(self):
        return self.coffee.description() + " + Milk"

class SugarDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 1
    
    def description(self):
        return self.coffee.description() + " + Sugar"

Mỗi decorator bao bọc một đối tượng khác và bổ sung chức năng riêng.

Dùng decorator để tạo món cà phê theo ý bạn:

# Bắt đầu với cà phê cơ bản
my_coffee = Coffee()
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

# Thêm sữa
my_coffee = MilkDecorator(my_coffee)
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

# Thêm đường
my_coffee = SugarDecorator(my_coffee)
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

Tạo một ví dụ khác có định dạng văn bản:

class Text:
    def __init__(self, content):
        self.content = content
    
    def render(self):
        return self.content

class BoldDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<b>{self.text.render()}</b>"

class ItalicDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<i>{self.text.render()}</i>"

# Tạo văn bản có định dạng từng bước
message = Text("Hello World")
message = BoldDecorator(message)
message = ItalicDecorator(message)

print(message.render())

Đầu ra:

Simple coffee: $5
Simple coffee + Milk: $7
Simple coffee + Milk + Sugar: $8
<i><b>Hello World</b></i>

Điểm chính: Mẫu Decorator bao bọc các đối tượng để thêm hành vi mới mà không thay đổi đối tượng ban đầu. Mỗi decorator giữ một tham chiếu đến đối tượng được bao bọc và bổ sung chức năng riêng. Nhờ đó, bạn có thể kết hợp nhiều decorator để linh hoạt ghép các tính năng mà không cần tạo nhiều lớp con.

challenge icon

Thử thách

Trung bình

Trong thử thách này, bạn sẽ triển khai mẫu thiết kế Decorator để tạo một hệ thống gọi cà phê linh hoạt cho quán cà phê. Mẫu Decorator cho phép bổ sung hành vi cho từng đối tượng một cách linh hoạt mà không ảnh hưởng đến hành vi của các đối tượng khác cùng lớp.

Bạn đang xây dựng một hệ thống cho quán cà phê cần có khả năng:

  • Cung cấp nhiều loại đồ uống (Espresso, Dark Roast, House Blend, Decaf)
  • Cho phép khách hàng thêm các món bổ sung (Milk, Mocha, Soy, Whipped Cream) vào đồ uống
  • Tính giá đồ uống kèm các món bổ sung
  1. Hoàn thiện lớp trừu tượng Beverage và các lớp triển khai cụ thể trong beverage.py
  2. Triển khai lớp trừu tượng CondimentDecorator và các lớp trang trí cụ thể trong condiment_decorator.py
  3. Triển khai các tình huống kiểm thử trong driver.py để xác minh phần triển khai của bạn

Tự làm thử

# Nhập tất cả các lớp cần thiết
from beverage import Espresso, HouseBlend, DarkRoast, Decaf
from condiment_decorator import Milk, Mocha, Soy, Whip

# Xử lý toàn diện các test case
test_case = input()

if test_case == "basic_beverage_test":
    # Kiểm tra chức năng cơ bản của đồ uống
    espresso = Espresso()
    print(f"Description: {espresso.get_description()}")
    print(f"Cost: ${espresso.cost():.2f}")

elif test_case == "all_beverages_test":
    # Kiểm tra tất cả các loại đồ uống
    beverages = [Espresso(), HouseBlend(), DarkRoast(), Decaf()]
    for beverage in beverages:
        print(f"{beverage.get_description()}: ${beverage.cost():.2f}")

elif test_case == "single_condiment_test":
    # Kiểm tra việc thêm một loại thành phần phụ vào đồ uống
    espresso = Espresso()
    espresso_with_milk = Milk(espresso)
    print(f"Description: {espresso_with_milk.get_description()}")
    print(f"Cost: ${espresso_with_milk.cost():.2f}")

elif test_case == "multiple_condiments_test":
    # Kiểm tra việc thêm nhiều thành phần phụ vào đồ uống
    house_blend = HouseBlend()
    decorated_coffee = Mocha(Soy(Whip(house_blend)))
    print(f"Description: {decorated_coffee.get_description()}")
    print(f"Cost: ${decorated_coffee.cost():.2f}")

elif test_case == "all_condiments_test":
    # Kiểm tra việc thêm tất cả thành phần phụ vào một đồ uống
    dark_roast = DarkRoast()
    fully_loaded = Milk(Mocha(Soy(Whip(dark_roast))))
    print(f"Description: {fully_loaded.get_description()}")
    print(f"Cost: ${fully_loaded.cost():.2f}")

elif test_case == "cost_calculation_test":
    # Kiểm tra cách tính giá cho các cách kết hợp khác nhau
    espresso = Espresso()
    
    # Thêm từng thành phần phụ
    with_milk = Milk(espresso)
    with_mocha = Mocha(espresso)
    
    print(f"Espresso: ${espresso.cost():.2f}")
    print(f"Espresso + Milk: ${with_milk.cost():.2f}")
    print(f"Espresso + Mocha: ${with_mocha.cost():.2f}")

elif test_case == "decoration_order_test":
    # Kiểm tra thứ tự thêm thành phần phụ có ảnh hưởng đến kết quả cuối cùng hay không
    decaf = Decaf()
    
    order1 = Milk(Mocha(decaf))
    order2 = Mocha(Milk(decaf))
    
    print(f"Order 1 - {order1.get_description()}: ${order1.cost():.2f}")
    print(f"Order 2 - {order2.get_description()}: ${order2.cost():.2f}")
    print(f"Same cost: {order1.cost() == order2.cost()}")

elif test_case == "complex_order_test":
    # Kiểm tra các đơn đồ uống phức tạp
    orders = [
        Mocha(Whip(Espresso())),
        Soy(Milk(HouseBlend())),
        Whip(Mocha(Soy(DarkRoast()))),
        Milk(Decaf())
    ]
    
    for i, order in enumerate(orders, 1):
        print(f"Order {i}: {order.get_description()}")
        print(f"Cost: ${order.cost():.2f}")
        print("---")

elif test_case == "double_condiment_test":
    # Kiểm tra việc thêm cùng một thành phần phụ nhiều lần
    espresso = Espresso()
    double_mocha = Mocha(Mocha(espresso))
    print(f"Description: {double_mocha.get_description()}")
    print(f"Cost: ${double_mocha.cost():.2f}")

elif test_case == "beverage_comparison_test":
    # So sánh giá của các loại đồ uống khác nhau khi thêm cùng loại nguyên liệu
    base_beverages = [Espresso(), HouseBlend(), DarkRoast(), Decaf()]
    
    print("All beverages with Milk + Mocha:")
    for beverage in base_beverages:
        decorated = Milk(Mocha(beverage))
        print(f"{decorated.get_description()}: ${decorated.cost():.2f}")

elif test_case == "condiment_cost_test":
    # Kiểm tra giá của từng loại nguyên liệu thêm vào
    espresso = Espresso()
    base_cost = espresso.cost()
    
    condiments = [
        ("Milk", Milk(espresso)),
        ("Mocha", Mocha(espresso)),
        ("Soy", Soy(espresso)),
        ("Whip", Whip(espresso))
    ]
    
    for name, decorated in condiments:
        additional_cost = decorated.cost() - base_cost
        print(f"{name} adds: ${additional_cost:.2f}")

elif test_case == "cheapest_most_expensive_test":
    # Tìm đồ uống cơ bản rẻ nhất và đắt nhất
    beverages = [
        ("Espresso", Espresso()),
        ("House Blend", HouseBlend()),
        ("Dark Roast", DarkRoast()),
        ("Decaf", Decaf())
    ]
    
    beverages.sort(key=lambda x: x[1].cost())
    print(f"Cheapest: {beverages[0][0]} - ${beverages[0][1].cost():.2f}")
    print(f"Most Expensive: {beverages[-1][0]} - ${beverages[-1][1].cost():.2f}")

elif test_case == "nested_decoration_test":
    # Kiểm tra các lớp trang trí lồng nhau nhiều tầng
    beverage = HouseBlend()
    # Thêm 5 lớp trang trí
    decorated = Whip(Soy(Mocha(Milk(Whip(beverage)))))
    print(f"Description: {decorated.get_description()}")
    print(f"Cost: ${decorated.cost():.2f}")
    
    # Đếm số nguyên liệu thêm vào
    description = decorated.get_description()
    condiment_count = description.count(" + ")
    print(f"Number of condiments: {condiment_count}")

elif test_case == "cost_breakdown_test":
    # Chi tiết từng khoản trong giá
    dark_roast = DarkRoast()
    print(f"Base Dark Roast: ${dark_roast.cost():.2f}")
    
    with_soy = Soy(dark_roast)
    print(f"+ Soy: ${with_soy.cost():.2f}")
    
    with_mocha = Mocha(with_soy)
    print(f"+ Mocha: ${with_mocha.cost():.2f}")
    
    with_whip = Whip(with_mocha)
    print(f"+ Whip: ${with_whip.cost():.2f}")
    
    final_cost = with_whip.cost()
    print(f"Final total: ${final_cost:.2f}")

elif test_case == "description_format_test":
    # Kiểm tra tính nhất quán của định dạng mô tả
    espresso = Espresso()
    test_combinations = [
        Milk(espresso),
        Mocha(Milk(espresso)),
        Whip(Mocha(Milk(espresso))),
        Soy(Whip(Mocha(Milk(espresso))))
    ]
    
    for combo in test_combinations:
        description = combo.get_description()
        print(f"Description: '{description}'")
        # Kiểm tra xem mô tả có bắt đầu bằng tên đồ uống cơ bản không
        starts_correctly = description.startswith("Espresso")
        print(f"Starts with base name: {starts_correctly}")
        print("---")
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 Python online