Дата и время публикации:
Основная цель композиции и наследования в Python, обеспечить повторное использование и эффективное применение кода. **Композиция и наследование классов** В Python, [понимание](https://realpython.com/inheritance-composition-python/) наследования и композиции является крайне важным для эффективной работы с экземплярами классов. Наследование позволяет моделировать взаимосвязи, где производный класс расширяет функциональность базового . А вот композиция моделирует взаимосвязи, где класс содержит экземпляр другого класса, чтобы создать более сложную структуру. Обе технологии программирования способствуют повторному использованию кода, но достигают того по своему: * композиция и наследование в программировании моделирует взаимосвязи между классам, позволяя повторно использовать код различными способами; * Модели или схемы наследования является взаимосвязью, позволяющей производным классам расширять функциональность базового; * Наследование достигается с помощью определения классов, которые являются производными от базовых классов, наследуя их интерфейс и реализацию. **Наследование классов** Модель представления классов в качестве ящиков, где один ящик находится в другом и имеющих взаимосвязь, показанную стрелкой вниз от производного к базовому с обозначением назначения -- расширение функциональности, которую принято обозначать словом "Extends". (Обычно, gjrf ). Так же, наследование классов приянто обозначать с использованием унифицированного языка моделирования UML (рисунок справа):
┌──────────────────────────┐ ┌─────────────┐
│ Derived сlass(base class)│ │ Base class │
├───────────────────┬──────┤ ├─────────────┤
│░░░░░░░░░░░░░░░░░░░│░░░░░░│ │ │
│░░░░░░░░░░Extends░░│░░░░░░│ │ │
│░░░░░░░░░░░░░┌─────▽──────┤ └──────△──────┘
│░░░░░░░░░░░░░│ Base class │ │
│░░░░░░░░░░░░░├────────────┤ ┌──────┴──────┐
│░░░░░░░░░░░░░│▓▓▓▓▓▓▓▓▓▓▓▓│ │Derived class│
│░░░░░░░░░░░░░│▓▓▓▓▓▓▓▓▓▓▓▓│ ├─────────────┤
│░░░░░░░░░░░░░│▓▓▓▓▓▓▓▓▓▓▓▓│ │ │
│░░░░░░░░░░░░░│▓▓▓▓▓▓▓▓▓▓▓▓│ │ │
└─────────────┴────────────┘ └─────────────┘
В тоже время, рисунок слева дает понимание, что в Python наследование будет влиять на поведение экземпляров производного класса, но и сам класс, так о включает в себя атрибуты, свойства и методы класса и триггеры с двойным подчеркиванием (double leading underscores trigger), которые позволяют изменить поведение базового класса.
**Композиция классов**
В отличии от модели наследования, композиция позволяет создавать сложные классы путем комбинирования экземпляров классов через взаимосвязь, которая начинается от ромба у композитного класса (далее -- композит) и заканчивается стрелкой на компонентном классе (далее -- компонент):
┌─────────────┐
│ Сomposite │
├─────────────┤
│ │
│ │
└──────◆──────┘
│ attach
V
┌─────────────┐
│ Сomponent │
├─────────────┤
│ │
│ │
└─────────────┘
Cоздание взаимосвязи между классом Сomponent и Сomposite осуществляется с использованием метода attach(), определенного в классе Сomposite .
**Шаблон проектирования Composite**
В Python, негласно сложился [шаблон проектирования Composite](https://sbcode.net/python/composite/), который является структурным шаблоном для иерархического управления.
Шаблон проектирования Composite,
* Позволяет представлять индивидуальные сущности в виде листьев и группы листьев одним и тем же.
* Является структурным шаблоном проектирования, который позволяет соединять экземпляры объектов в одну изменяемую структуру в виде дерева.
* Является наилучшим, если вам нужна опция обмена иерархическими отношениями.
* Позволяет добавлять / удалять компоненты в/из иерархии.
* Обеспечивает гибкую структуру.
_Интерфейс компонента_: интерфейс, у которого все листья и композиты должны быть реализованы.
_Лист_: Отдельный экземпляр, что может существовать внутри или за пределами композита.
_Композит_: Коллекция листьев или других композитов
Композитная диаграмма UML
------------------------
┌─────────────┐ ┌─────────────┐ Иерархическое дерево на основе композиции
│ │ │ Сomponent │<───┐ -----------------------------------------
│ Client App ├───────────▶├─────────────┤ │ extends ┌───────────┐ extends
│ │ │ │ │ ┌─────────▷ Сomponent ◁───────────┬───────┐
└─────────────┘ ┌──▷ │ │ │ └───────────┘ │ │
│ └──────△──────┘ │ │ │ │
extends │ │ │ ┌──┴─────────────┐ attach() ┌───────┴────┐ │
│ │ │ │ Сomposite_top ├──────────▶│ Composite │ │
┌─────────────┐│ ┌──────┴──────┐ │ └────────────────┘ └──────────▲─┘ │
│ Leaf ├┘ │ Сomposite ◆────┘ attach() │ │
├─────────────┤ ├─────────────┤ ┌─┴────┴──────┐
│ │ │ │ │ Leaf │
│ │ │ │ └─────────────┘
└─────────────┘ └─────────────┘
На картинке показано композиционное иерархическое дерево со всеми возможными комбинированиями и взаимосвязями:
* наследования к классу Сomponent, который является абстрактным базовым классом Сomposite_top, Composite и Leaf ;
* композиции между классами Сomposite_top, Composite и Leaf с использованием интерфейса attach() .
Шаблон проектирования Composite реализуется в Python c помощью модуля [abc — Abstract Base Classes](https://docs.python.org/3/library/abc.html), обеспечивающий инфраструктурой для определения абстрактных базовых классов (abstract base classes, ABCs) .
Абстрактных базовые классы:
* дополняют утиную типизацию, обеспечивая способ для определения интерфейсов, когда другие методы подобные hasattr() будут неуклюжими и не совсем правильными (например с [магическими методами](https://docs.python.org/3/reference/datamodel.html#special-lookup));
* вводят виртуальные подклассы, являющиеся классами, которые не наследуются от класса, но все же распознаются recognized by isinstance() and issubclass().
Python поставляется со многими встроенными классами ABC, такими как :
* Структуры данных -- в модуле [collections.abc](https://docs.python.org/3/library/collections.abc.html#module-collections.abc))
* Чисел -- в модуле [numbers](https://docs.python.org/3/library/numbers.html#module-numbers))
* Потоковой обработки данных -- в модуле [io](https://docs.python.org/3/library/io.html#module-io)),
* В поиске и загрузке импорта -- в модуле [importlib.abc module](https://docs.python.org/3/library/importlib.html#module-importlib.abc),
В данной статье, речь пойдет о создании своего собственного класса ABC, как будет показано ниже, на основе модуля [abc](https://docs.python.org/3/library/abc.html) и класса [abc.ABC](https://docs.python.org/3/library/abc.html#abc.ABC).¶
**Композиция функций**
В математике, композиция функций [является](https://python-course.eu/advanced-python/function-composition-in-python.php) операцией, которая комбинирует две или более функций в новой создаваемой функции. Композиция функций обозначается маленьким кругом (∘) или просто путем замещения одной функции другой. Данные две функции, например, f и g, являются композицей и обозначается как (f ∘ g)(x), определяется следующим образом:
f: A -> B
g: B -> C
Композиция из этих двух функций (f ∘ g) прорждает новую функцию, которая сопоставляет элементы из набора А к набору С . Эта композиция определяется для элемента x в наборе A следующим образом:
(f ∘ g)(x)=f(g(x))
Композиция функций в Python является весьма схожей с математической концепцией, где композиция является комбинированием двух или более функций внутри одной, что в действительности является процессом превращения функции, которая берет множество аргуметов в последовательности функций, где каждой из них доступен один аргументе.
## 2. Использование
## 2.1 Реализация шаблона проектирования Composite
Приводится на примере использования модуля [abc — Abstract Base Classes](https://docs.python.org/3/library/abc.html) и класса [abc.ABC](https://docs.python.org/3/library/abc.html#abc.ABC).¶
Композиционная диаграмма UML
----------------------------
┌────────────────────────────┐
┌───▷ IСomponent │<───┐
│ ├────────────────────────────┤ │
│ │ reference_to_parent: type │ │
│ │────────────────────────────│ │
│ │ method(type): type │ │
│ │────────────────────────────│ │
│ │ detach(type): type │ │ attach()
│ └───────────────△────────────┘ │
│ │ │
│ │ │
┌────────────────────────────┐ │ ┌───────────────┴────────────┐ │
│ Leaf ├─┘ │ IСomposite ◆────┘
├────────────────────────────┤ ├────────────────────────────┤
│ type method(type) │ │ components: list │
│────────────────────────────│ │────────────────────────────│
│ type detach(type) │ │ reference_to_parent: type │
└────────────────────────────┘ │────────────────────────────│
│ method(type): type │
│────────────────────────────│
│ detach(type): type │
│────────────────────────────│
│ attach(type): type │
│────────────────────────────│
│ delete(type): type │
└────────────────────────────┘
Шаблон проектирования Composite будет вестись на программных компонентах модуля [abc](https://docs.python.org/3/library/abc.html) :
from abc import ABCMeta, abstractmethod
Где
* [ABCMeta](https://docs.python.org/3/library/abc.html#abc.ABCMeta) -- Метакласс для определения абстрактного базового класса;
* [abstractmethod](https://docs.python.org/3/library/abc.html#abc.abstractmethod) -- Декоратор абстрактного метода.
**Класс IСomponent**
class IComponent(metaclass=ABCMeta):
reference_to_parent = None
@staticmethod
@abstractmethod
def method():
"A method each Leaf and composite container should implement"
@staticmethod
@abstractmethod
def detach():
"Called before a leaf is attached to a composite"
Является базовым классом и определяет или вернее объявляет два абстрактных метода, реализация которых будет выполнена позднее в производных классах Leaf и IСomposite :
* method() должен реализовываться в производных классах Leaf и IСomposite;
* detach() вызывается до присоединения экземпляра Leaf к экземпляру IСomposite .
**Класс Leaf**
Реализует method() и detach()
class Leaf(IComponent):
def method(self):
parent_id = (id(self.reference_to_parent)
if self.reference_to_parent is not None else None)
print(
f"\t\tid:{id(self)}\tParent:\t{parent_id}"
)
def detach(self):
"Detaching this leaf from its parent composite"
if self.reference_to_parent is not None:
print ( f"\t\tdetach from\t\t"
f"Parent: {id(self.reference_to_parent)}")
self.reference_to_parent.delete(self)
Где
* интерфейс method() выводит свой и родительский id(),
* интерфейс detach() отсоединяет себя от родительского экземпляра.
Управление в части отсоединения от родительского можно осуществлять в присоединяемом экземпляре класса Leaf , такое же поведение интерфейс detach() можно наблюдать и в классе IComposite, который присоединяется к аналогичному экземпляру этого класса.
**Класс IComposite**
Композитный класс, реализуемый на базе абстрактного класса IСomponent, содержащий интерфейсы присоединения и удаления компонентных экземпляров на базе класса IComponent .
class IComposite(IComponent):
"A composite can contain leaves and composites"
def __init__(self):
self.components = []
def method(self):
parent_id = (id(self.reference_to_parent)
if self.reference_to_parent is not None else None)
print(
f"\tid:{id(self)}\tParent:\t{parent_id}\t"
f"Components:{len(self.components)}")
for component in self.components:
component.method()
def attach(self, component):
"""
Detach leaf/composite from any current parent reference and
then set the parent reference to this composite (self)
"""
print ( f"\tattach \t\tComponent: {id(component)}")
component.detach()
component.reference_to_parent = self
self.components.append(component)
def delete(self, component):
"Removes leaf/composite from this composite self.components"
self.components.remove(component)
def detach(self):
"Detaching this composite from its parent composite"
if self.reference_to_parent is not None:
self.reference_to_parent.delete(self)
self.reference_to_parent = None
Где
* интерфейс delete() удаляет экземпляры ранее присоединенные экземпляры компонентов,
* интерфейс attach() не только присоединяет компонент с предварительной операцией отсоединения от другого композитного экземпляра.
**Клиентское приложение**
Создается на базе трех три экземпляра (два -- от класса Composite, один -- от класса Leaf ) на основе взаимосвязей наследования с классом IComponent:
LEAF = Leaf()
COMPOSITE_TOP=IComposite()
COMPOSITE = IComposite()
print(f"LEAF\t\tid:{id(LEAF)}")
print(f"COMPOSITE_TOP\tid:{id(COMPOSITE_TOP)}")
print(f"COMPOSITE\tid:{id(COMPOSITE)}")
print()
Образует композитные взаимосвязи между двумя Composite и Leaf :
COMPOSITE_TOP.attach(COMPOSITE)
COMPOSITE.attach(LEAF)
print()
Вызывает методы двух Composite и Leaf :
LEAF.method()
COMPOSITE.method()
COMPOSITE_TOP.method()
Разрывает связь между Composite и Leaf :
LEAF.detach()
В результате на выводе получается следующее:
LEAF id:140336838737632
COMPOSITE_TOP id:140336838737440
COMPOSITE id:140336838736960
attach Component: 140336838737632
attach Component: 140336838736960
id:140336838737632 Parent: 140336838736960
id:140336838736960 Parent: 140336838737440 Components:1
id:140336838737632 Parent: 140336838736960
id:140336838737440 Parent: None Components:1
id:140336838736960 Parent: 140336838737440 Components:1
id:140336838737632 Parent: 140336838736960
detach from Parent: 140336838736960
## 2.2 Композиция функций
В Python, выполнение композиции функций осуществляется путем определения новой функции, которая комбинирует две или более функций, чтобы создать одну композитную функцию. Как показано в данном примере:
>>>
>>>
>>> def f(x):
... return 2 * x
...
>>> def g(x):
... return x + 3
...
>>> # Define a composite function (f ∘ g)
>>> def composite_function(x):
... return f(g(x))
...
>>> # Test the composite function
>>> result = composite_function(5)
>>> print(result)
16
>>>