Благодаря книге «Паттерны проектирования: Elements of Reusable Object-Oriented Softwar» (авторы Эрих Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссидес), шаблоны (паттерны) приобрели популярность в компьютерной науке. В отрасли ее называют Gangs of Four – «Банда четырех».
Эта книга учит мозг улавливать паттерны, которые в дальнейшем можно применить к существующему коду, чтобы повысить эффективность вашей разработки. Следует изучить как можно больше паттернов. Это позволит выбрать нужный, когда его использование будет необходимо.
Большая часть книги посвящена паттернам для языка программирования Java и C++. Однако, в этой статье мы сделаем упор на использование паттернов в языке Python. Рассмотрим несколько шаблонов проектирования из каждой категории, согласно изначально предложенной классификации внутри книги, которые показались мне наиболее интересными в контексте программирования на Python.
Что такое паттерн проектирования?
В разработке программного обеспечения паттерн – это общее, многократно используемое решение проблемы, которая часто встречается внутри конкретной ситуации. Это похоже на готовые чертежи, которые можно использовать для решения проблемы в вашем коде.
Стоит отметить, что нельзя применять паттерн проектирования так же, как используется функция из импортированной библиотеки. Вместо этого, вы должны следовать концепции паттерна и реализовать решение, которое соответствует требованиям вашей программы. Паттерн – это не фрагмент кода, а общая концепция, которая описывает, как решить конкретную повторяющуюся проблему.
Классификация паттернов проектирования
Изначально существовало две основные классификации паттернов проектирования:
- Какую проблему решает паттерн.
- Как относится паттерн к классам или объектам.
Принимая во внимание первую классификацию, паттерны можно разделить на три группы:
- Порождающие – предоставляют возможность создания контролируемым образом, инициализации и конфигурации объектов, классов и типов данных на основе требуемых критериев.
- Структурные – помогают организовать структуры связанных объектов и классов, предоставляя новые функциональные возможности.
- Поведенческие – направлены на выявление общих моделей взаимодействия между объектами.
Позже появились новые паттерны проектирования, из которых можно выделить еще одну категорию:
Concurrency (параллелизм) – это тот тип паттернов проектирования, который имеет дело с многопоточной парадигмой программирования.
Паттерн 1: Синглтон
Синглтон (одиночка) – это паттерн проектирования, цель которого ограничить возможность создания объектов данного класса одним экземпляром. Он обеспечивает глобальность до одного экземпляра и глобальный доступ к созданному объекту.
Примеры использования
- Класс в вашей программе имеет только один экземпляр, доступный всем клиентам. Например, один объект базы данных, разделяемый различными частями программы.
- В случае если вам необходим более строгий контроль над глобальными переменными.
Пример кода:
Первый наивный подход (naive approach):
Что не так с этим кодом?
Он нарушает причины изменения, которые приняты в концепции SRP (single responsibility principle). Необходимо помнить, что доступ к экземплярам класса осуществляется только методом get_instance()
. Проблема в дескрипторе, куда пишутся логи со стороны класса Logger
.
Итак, проблемы из предыдущего примера решены. Но возможно ли найти более оптимальный способ (без наследования классов)?
Давайте попробуем.
Все работает. Однако, надо сделать еще одну настройку – подготовить программу к работе в многопоточной среде.
Вывод:
Подведем итоги. Особенности использования Синглтона:
- Класс имеет только один экземпляр;
- Вы получаете глобальную точку доступа к этому экземпляру;
- Синглтон инициализируется только при первом запросе;
- Маскирует плохой дизайн до определенного момента. Это одна из причин, почему многие считают синглтон антипаттерном.
Паттерн 2: Декоратор
Декоратор – это структурный паттерн. Цель которого – предоставление новых функциональных возможностей классам и объектам во время выполнения кода.
Чаще всего декоратор представляет собой абстрактный класс, принимающий в конструкторе объект, функциональность которого мы хотим расширить. Но в Python есть и встроенный механизм декораторов, который можно использовать.
Случаи использования
- Необходимость назначить дополнительные обязанности объектам во время выполнения, не ломая код, который использует эти объекты;
- По каким-то причинам невозможно расширить «цепочку обязанностей» объекта через наследование.
Пример кода
Используя декораторы, вы можете обернуть объекты несколько раз, поскольку и цель, и декораторы реализуют один и тот же интерфейс.
Получаемый объект будет обладать объединенной и сложенной функциональностью всех декораторов.
Вывод:
Практичный пример с использованием встроенного механизма декораторов:
Вывод:
Без использования декоратора кэша для функции, которая рекурсивно вычисляет n-й член ряда Фибоначчи, трудно вычислить результат для значения 100 за все время работы.
Подведем итоги. Возможности декоратора:
- Расширение поведения объекта без создания подкласса;
- Добавление или удаление обязанности объекта во время выполнения;
- Объединение нескольких моделей поведения, путем применения к объекту нескольких декораторов;
- Разделение монолитного класса, который реализует множество вариантов поведения на более мелкие классы;
При применении этого паттерна возникают следующие сложности:
- Применение одной конкретной обертки (wrapper) из центра стека (stack);
- Реализация декоратора, при исключении его зависимости от порядка, в котором обертки уложены в стек.
Паттерн 3: Итератор
Итератор – это поведенческий паттерн. Его цель – позволить вам обходить элементы коллекции, не раскрывая ее базовое представление.
Чтобы реализовать итератор в Python, у нас есть два возможных варианта:
- Реализовать в классе специальные методы
__iter__
и__next__
. - Использовать генераторы.
Примеры использования
- Коллекция имеет сложную структуру. Необходимо скрыть ее от клиента из соображений удобства или безопасности;
- Требуется сократить дублирование обходного кода по всему приложению;
- Обход элементов различных структур данных;
- Изначально неизвестны детали структуры данных.
Пример кода
Создание пользовательской коллекции с итератором алфавитного порядка:
Вывод:
Следующий пример относится к генератору, который представляет собой особый вид функции. Функция может быть приостановлена и возобновлена с того места, где была совершена пауза. На основе сохраненного состояния можно возвращать различные значения при последующих вызовах генератора.
Вывод:
Подведем итоги. Возможности итератора:
- Очистить клиентский код и коллекции, вынеся код обхода в отдельные классы;
- Реализовать новые типы коллекций и итераторов с передачей их в существующий код без нарушений;
- Обходить одну и ту же коллекцию с помощью нескольких итераторов параллельно, учитывая, что каждый из них хранит информацию о состоянии итерации;
- Возможность отложить итерацию и продолжить ее по мере необходимости;
Использование этого паттерна будет лишним, если ваше приложение работает только с простыми коллекциями. Более того, использование итератора может быть менее эффективным, чем прямой обход элементов какой-либо специализированной коллекции.
Заключение
Знание паттернов важно для современного разработчика. Оно помогает решить проблемы внутри вашего кода, используя принципы объектно-ориентированного программирования. Этот материал дает возможность познакомиться с основами работы с паттернами.
Хочу освоить больше паттернов, этому где-нибудь учат?
9 февраля стартует курс «Архитектуры и шаблоны проектирования», на котором вы научитесь:
- строить архитектуры приложений, которые позволяют не снижать скорость разработки по мере развития проекта;
- писать модульные тесты на Mock-объектах;
- применять SOLID принципы не только в объектно-ориентированных языках;
- использовать CI и IoC контейнеры.
Что нужно для старта?
Для старта достаточно знать любой объектно-ориентированный язык программирования: Python, Java, PHP, C++, JavaScript, C# и др.
Комментарии