Menu
Coddy logo textTech

Паттерн Декоратор

Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 96 из 110.

Паттерн Декоратор позволяет динамически добавлять объектам новые обязанности, оборачивая их в специальные объекты, называемые декораторами. В отличие от наследования, которое добавляет поведение на этапе компиляции, декораторы позволяют расширять функциональность объектов во время выполнения, не изменяя исходный класс.

Основная идея заключается в том, что и оригинальный объект, и декораторы реализуют один и тот же интерфейс.

Декоратор оборачивает оригинальный объект, делегирует ему выполнение и добавляет собственное поведение до или после:

abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

class SugarDecorator implements Coffee {
  final Coffee _coffee;
  SugarDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Sugar';

  @override
  double getCost() => _coffee.getCost() + 0.2;
}

void main() {
  Coffee coffee = SimpleCoffee();
  coffee = MilkDecorator(coffee);
  coffee = SugarDecorator(coffee);

  print(coffee.getDescription());  // Simple Coffee, Milk, Sugar
  print(coffee.getCost());         // 2.7
}

Каждый декоратор оборачивает предыдущий объект и расширяет его возможности. Вы можете комбинировать несколько декораторов в любом порядке, создавая гибкое поведение без необходимости создания подкласса для каждой возможной комбинации. Этот паттерн идеально подходит, когда вам нужно добавить функциональность объектам, не затрагивая другие объекты того же класса.

challenge icon

Задание

Легко

Давайте создадим систему заказа пиццы, используя паттерн Декоратор! Вы создадите базовую пиццу, которую можно дополнять различными начинками, где каждая начинка оборачивает предыдущий элемент и увеличивает как описание, так и цену.

Вы организуете свой код в двух файлах:

  • pizza.dart: Этот файл содержит ваш интерфейс пиццы и все классы-декораторы. Начните с абстрактного класса Pizza, который определяет два метода: String getDescription() и double getPrice(). Затем создайте класс PlainPizza, который реализует Pizza — он возвращает Plain Pizza в качестве описания и стоит 8.0. Теперь создайте три класса-декоратора, каждый из которых оборачивает Pizza и дополняет её:
    • CheeseDecorator — добавляет , Cheese к описанию и 1.5 к цене
    • PepperoniDecorator — добавляет , Pepperoni к описанию и 2.0 к цене
    • MushroomDecorator — добавляет , Mushrooms к описанию и 1.25 к цене
    Каждый декоратор должен реализовывать Pizza, принимать Pizza в своем конструкторе и делегировать выполнение обернутой пицце перед добавлением собственного вклада.
  • main.dart: Импортируйте файл с пиццей и продемонстрируйте, как декораторы накладываются друг на друга для создания индивидуального заказа. Начните с PlainPizza, затем оберните её в CheeseDecorator, затем оберните результат в PepperoniDecorator и, наконец, оберните всё в MushroomDecorator. Выведите описание итоговой пиццы, а затем выведите Total: $, за которым следует цена.

Обратите внимание, как каждый декоратор строится на основе предыдущего — вы создаете пиццу со всеми начинками, накладывая простые декораторы с единственной ответственностью!

Ожидаемый результат:

Plain Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.75

Шпаргалка

Паттерн Декоратор динамически добавляет объектам новые обязанности, оборачивая их в объекты-декораторы. Как исходный объект, так и декораторы реализуют один и тот же интерфейс.

Ключевые компоненты:

  • Абстрактный класс или интерфейс, определяющий общие методы
  • Конкретный класс, реализующий базовую функциональность
  • Классы декораторов, которые оборачивают исходный объект и расширяют его

Каждый декоратор:

  • Реализует тот же интерфейс, что и декорируемый объект
  • Хранит ссылку на обернутый объект
  • Делегирует вызовы обернутому объекту
  • Добавляет собственное поведение до или после делегирования
abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

// Наслоение нескольких декораторов
Coffee coffee = SimpleCoffee();
coffee = MilkDecorator(coffee);
coffee = SugarDecorator(coffee);

Декораторы можно комбинировать в любом порядке, создавая гибкое поведение без необходимости создания подклассов для каждой возможной комбинации.

Попробуйте сами

import 'pizza.dart';

void main() {
  // TODO: Создать PlainPizza
  
  // TODO: Обернуть в CheeseDecorator
  
  // TODO: Обернуть это в PepperoniDecorator
  
  // TODO: Обернуть всё в MushroomDecorator
  
  // TODO: Вывести описание итоговой пиццы
  
  // TODO: Вывести "Total: $" и цену
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование