{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Паттерн Декоратор\n", "\n", "Паттерн декоратор относится к классу структурных паттернов проектирования. Его основная задача -- динамическое подключение дополнительной функциональности к объекту. При этом для подключения дополнительной функциональности используется не сложная иерархия подклассов, что является классическим решением данной задачи, а отдельная иерархия декораторов.\n", "\n", "Каждый из видов дополнительной функциональности, которая может быть добавлена к объекту, помещается в отдельный класс. Эти классы сами по себе небольшие, поэтому в них легко разобраться.\n", "\n", "В паттерн \"Декоратор\" входят оборачиваемый объект и сама иерархия декораторов. Каждый из декораторов реализует какое-то одно функциональное свойство. Это позволяет соблюдать один из SOLID принципов -- принцип единственной ответственности. Так мы можем подключить к классу только ту функциональность, которая необходима ему в данный момент. Для подключения нескольких\n", "функциональных свойств можно последовательно использовать несколько декораторов.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Структура декораторов\n", "\n", "\n", "\n", "Для создания паттерна \"Декоратор\" необходимы следующие классы:\n", "\n", "* Абстрактный объект (Component)\n", "* Оборачиваемый объект (на UML-диаграмме ConcreteComponent)\n", "* Абстрактный декоратор (Decorator)\n", "* Конкретный декоратор (ConcreteDecorator)\n", "\n", "Как видно из диаграммы, все декораторы по сути являются объектами, подобными самому компоненту. Из этого следует, что они реализуют одинаковый интерфейс. Согласно принципу подстановки Барбары Лисков у пользователя должна быть возможность корректного использования объекта-декоратора (то есть объекта, обернутого в декоратор), не зная об этом.\n", "\n", "Тут находится одно из слабых мест паттерна. Интерфейс объекта и интерфейс модифицированного объекта одинаковы. Это не всегда удобно, иногда для модифицированного объекта требуется отдельный интерфейс." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Другие похожие паттерны:\n", "\n", "### Стратегия\n", "\n", "Также позволяет динамически добавлять поведение объекту. Так же, как и в декораторе, стратегии реализуются в отдельных классах Однако, в отличие от декоратора, декорируемый класс не оборачивается в стратегию, а стратегия, как компонент, встраивается в основной класс.\n", "\n", "### Цепочка обязанностей\n", "\n", "Цепочка обязанностей так же в чем-то близкий к декоратору класс. Она так же задает множество обработчиков некоторого события. Отличие от декоратора заключается в том, что в цепочке обязанностей событие обрабатывает только один из классов, тогда как в декораторе все классы-декораторы обрабатывают событие." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Использование паттерна Декоратор\n", "\n", "При использовании паттерна декорируемый объект оборачивается в декоратор. Таким образом получается вложенная структура из декораторов. Отменить действие декоратора можно, если достать базовый объект из декоратора. Это можно сделать, обратившись к decorated_object.base. Аналогичным образом можно отменить эффект декоратора из середины иерархии. Для этого изменим базовый объект у внешнего декоратора на базовый объект декоратора, который необходимо удалить. Принцип похож на удаление элемента из середины односвязного списка." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Пример использования Декоратора" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from abc import ABC, abstractmethod\n", "\n", "class Creature(ABC):\n", " @abstractmethod\n", " def feed(self):\n", " pass\n", " \n", " @abstractmethod\n", " def move(self):\n", " pass\n", " \n", " @abstractmethod\n", " def make_noise(self):\n", " pass\n", " \n", "class Animal(Creature):\n", " def feed(self):\n", " print(\"I eat grass\")\n", " \n", " def move(self):\n", " print(\"I walk forward\")\n", " \n", " def make_noise(self):\n", " print(\"WOOO!\")\n", " \n", "class AbstractDecorator(Creature):\n", " def __init__(self, obj):\n", " self.obj = obj\n", " \n", " def feed(self):\n", " self.obj.feed()\n", " \n", " def move(self):\n", " self.obj.move()\n", " \n", " def make_noise(self):\n", " self.obj.make_noise()\n", " \n", "class Swimming(AbstractDecorator):\n", " def move(self):\n", " print(\"I swim\")\n", " \n", " def make_noise(self):\n", " print(\"...\")\n", " \n", "class Flying(AbstractDecorator):\n", " def move(self):\n", " print(\"I fly\")\n", " \n", " def make_noise(self):\n", " print(\"QUAAA!\")\n", " \n", "class Predator(AbstractDecorator):\n", " def feed(self):\n", " print(\"I eat other animals\")\n", " \n", "class Fast(AbstractDecorator):\n", " def move(self):\n", " self.obj.move()\n", " print(\"Fast!\")" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "I eat grass\n", "I walk forward\n", "WOOO!\n", "\n", "I eat grass\n", "I swim\n", "...\n", "\n", "I eat other animals\n", "I swim\n", "...\n", "\n", "I eat other animals\n", "I swim\n", "Fast!\n", "...\n", "\n", "I eat other animals\n", "I swim\n", "Fast!\n", "Fast!\n", "...\n", "\n" ] } ], "source": [ "animal = Animal()\n", "animal.feed()\n", "animal.move()\n", "animal.make_noise()\n", "print()\n", "\n", "animal = Swimming(animal)\n", "animal.feed()\n", "animal.move()\n", "animal.make_noise()\n", "print()\n", "\n", "animal = Predator(animal)\n", "animal.feed()\n", "animal.move()\n", "animal.make_noise()\n", "print()\n", "\n", "animal = Fast(animal)\n", "animal.feed()\n", "animal.move()\n", "animal.make_noise()\n", "print()\n", "\n", "\n", "animal = Fast(animal)\n", "animal.feed()\n", "animal.move()\n", "animal.make_noise()\n", "print()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }