Паттерн Декоратор
Часть раздела Object Oriented Programming путешествия по Python на Coddy — урок 51 из 64.
Паттерн Декоратор динамически добавляет объектам новую функциональность без изменения их структуры. Он оборачивает объекты для расширения их поведения.
Вот простой пример кофе:
class Coffee:
def cost(self):
return 5
def description(self):
return "Simple coffee"Создайте декораторы, которые добавляют новые возможности к кофе:
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"Каждый декоратор оборачивает другой объект и добавляет свою собственную функциональность.
Используйте декораторы для создания настраиваемого кофе:
# Начните с обычного кофе
my_coffee = Coffee()
print(f"{my_coffee.description()}: ${my_coffee.cost()}")
# Добавьте молоко
my_coffee = MilkDecorator(my_coffee)
print(f"{my_coffee.description()}: ${my_coffee.cost()}")
# Добавьте сахар
my_coffee = SugarDecorator(my_coffee)
print(f"{my_coffee.description()}: ${my_coffee.cost()}")Создайте еще один пример с форматированием текста:
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>"
# Пошаговое создание форматированного текста
message = Text("Hello World")
message = BoldDecorator(message)
message = ItalicDecorator(message)
print(message.render())Вывод:
Simple coffee: $5
Simple coffee + Milk: $7
Simple coffee + Milk + Sugar: $8
<i><b>Hello World</b></i>Ключевой момент: Паттерн Декоратор (Decorator Pattern) оборачивает объекты для добавления нового поведения без изменения исходного объекта. Каждый декоратор хранит ссылку на обернутый объект и добавляет свою собственную функциональность. Это позволяет комбинировать несколько декораторов для гибкого сочетания функций без создания множества подклассов.
Задание
СреднеВ этом испытании вы реализуете паттерн проектирования Декоратор (Decorator) для создания гибкой системы заказа кофе для кофейни. Паттерн Декоратор позволяет динамически добавлять поведение отдельным объектам, не влияя на поведение других объектов того же класса.
Вы строите систему для кофейни, которой необходимо:
- Предлагать различные виды напитков ("Espresso", "Dark Roast", "House Blend Coffee", "Decaf Coffee")
- Позволять клиентам добавлять добавки (" + Milk", " + Mocha", " + Soy", " + Whip") к своим напиткам
- Рассчитывать стоимость напитков с учетом добавленных ингредиентов
- Завершите абстрактный класс
Beverageи его конкретные реализации вbeverage.py - Реализуйте абстрактный класс
CondimentDecoratorи конкретные декораторы вcondiment_decorator.py - Реализуйте тестовые сценарии в
driver.py, чтобы проверить вашу реализацию
Шпаргалка
Паттерн Декоратор динамически добавляет объектам новую функциональность, не изменяя их структуру, путем оборачивания объектов для расширения их поведения.
Базовая структура на примере кофе:
class Coffee:
def cost(self):
return 5
def description(self):
return "Simple coffee"Создайте декораторы, которые оборачивают и расширяют функциональность:
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"Объединяйте декораторы в цепочки для создания настроенных объектов:
# Начинаем с простого кофе
my_coffee = Coffee()
# Добавляем декораторы
my_coffee = MilkDecorator(my_coffee)
my_coffee = SugarDecorator(my_coffee)
print(f"{my_coffee.description()}: ${my_coffee.cost()}")
# Вывод: Simple coffee + Milk + Sugar: $8Пример форматирования текста:
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>"
# Цепочка декораторов
message = Text("Hello World")
message = BoldDecorator(message)
message = ItalicDecorator(message)
print(message.render())
# Вывод: <i><b>Hello World</b></i>Основные преимущества: позволяет гибко комбинировать функции без создания множества подклассов, сохраняет ссылку на обернутый объект и динамически добавляет функциональность.
Попробуйте сами
# Импорт всех необходимых классов
from beverage import Espresso, HouseBlend, DarkRoast, Decaf
from condiment_decorator import Milk, Mocha, Soy, Whip
# Обработчик комплексных тестовых случаев
test_case = input()
if test_case == "basic_beverage_test":
# Тест базовой функциональности напитка
espresso = Espresso()
print(f"Description: {espresso.get_description()}")
print(f"Cost: ${espresso.cost():.2f}")
elif test_case == "all_beverages_test":
# Тест всех типов напитков
beverages = [Espresso(), HouseBlend(), DarkRoast(), Decaf()]
for beverage in beverages:
print(f"{beverage.get_description()}: ${beverage.cost():.2f}")
elif test_case == "single_condiment_test":
# Тест декорирования одной добавкой
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":
# Тест декорирования несколькими добавками
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":
# Тест всех добавок на одном напитке
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":
# Тест расчета стоимости для различных комбинаций
espresso = Espresso()
# Одиночные добавки
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":
# Тест того, что порядок декорирования не влияет на конечный результат
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":
# Тест сложных заказов напитков
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":
# Тест добавления одной и той же добавки несколько раз
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":
# Сравнение стоимости разных напитков с одинаковыми добавками
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":
# Тест стоимости отдельных добавок
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":
# Поиск самого дешевого и самого дорогого базового напитка
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":
# Тест глубоко вложенных декораций
beverage = HouseBlend()
# Добавление 5 слоев декораций
decorated = Whip(Soy(Mocha(Milk(Whip(beverage)))))
print(f"Description: {decorated.get_description()}")
print(f"Cost: ${decorated.cost():.2f}")
# Подсчет количества добавок
description = decorated.get_description()
condiment_count = description.count(" + ")
print(f"Number of condiments: {condiment_count}")
elif test_case == "cost_breakdown_test":
# Детальная разбивка стоимости
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":
# Тест согласованности форматирования описания
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}'")
# Проверка, начинается ли описание с названия базового напитка
starts_correctly = description.startswith("Espresso")
print(f"Starts with base name: {starts_correctly}")
print("---")В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыПараметр selfМетодыАтрибутыМетод-конструктор (__init__)Повторение — Простой калькулятор4Наследование
Базовое наследованиеФункция super()Переопределение методовМножественное наследованиеПорядок разрешения методовИтоги — Иерархия сотрудников7Специальные методы
Введение в магические методыПерегрузка операторовМагические методы контейнеровПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy5Полиморфизм
Снова о переопределении методовУтиная типизацияАбстрактные классы и методыПроектирование интерфейсовИтоги — Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияМиксиныСтатические методы и методы классаДекораторы классовМенеджеры контекста11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Свойства классов
Переменные экземпляра и классаДекораторы свойствПриватные атрибутыИтоги — Менеджер банковского счета6Инкапсуляция
Public, Protected, Private члены классаМодификаторы доступаСокрытие данныхДекораторы property: продвинутый уровеньИтоги: Система учета студентов12Проект: Управление библиотекой
Обзор проектаКлассы Book и User