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.
Thử thách
Trung bìnhTrong 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
- Hoàn thiện lớp trừu tượng
Beveragevà các lớp triển khai cụ thể trongbeverage.py - Triển khai lớp trừu tượng
CondimentDecoratorvà các lớp trang trí cụ thể trongcondiment_decorator.py - 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("---")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
1Nền tảng về lập trình hướng đối tượng
Tệp bên ngoàiGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTham số selfPhương thứcThuộc tínhPhương thức khởi tạo (__init__)Ôn tập: máy tính đơn giản4Kế thừa
Kế thừa cơ bảnHàm super()Ghi đè phương thứcKế thừa đa lớpThứ tự phân giải phương thứcÔn tập: hệ thống phân cấp nhân viên2Decorator
Giới thiệu về decoratorDecorator thuộc tínhDecorator phương thức tĩnhDecorator phương thức lớp5Tính đa hình
Xem lại cách ghi đè phương thứcKiểu gõ vịtLớp và phương thức trừu tượngThiết kế giao diệnÔn tập: máy tính hình học8Các khái niệm OOP nâng cao
Kết hợp và kế thừaMixinPhương thức tĩnh và phương thức lớpDecorator lớpTrình quản lý ngữ cảnh11Mẫ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ổng hợpTự luyện tập: Trình biên dịch Python online