Паттерны проектирования
Данной статьей мы начинаем серию статей, посвященных паттернам проектирования.
Статьи рассчитаны на тех, кто уже хорошо знает ООП.
Что такое паттерны в программировании
Ну, что ж, давайте сначала разберемся что такое паттерн. А затем плавно перейдем к такому понятию как «паттерны в программировании».
Паттерн — это повторяющийся элемент в различных сферах жизни.
Пример 1: окрас тигра — это паттерн.
Пример 2: Коробка передач — это паттерн.
В программировании, хотя каждая задача и каждая программа уникальна, у многих из них все же есть общие черты. Разработчики заметили эти закономерности, и выделили те, что наиболее часто встречаются, в паттерны (шаблоны проектирования, шаблоны программирования). В паттернах предлагается в том числе наиболее оптимальные способы реализации той или иной задачи.
Теперь, вместо того чтобы выдумывать велосипед заново, можно воспользоваться знаниями людей, которые уже решали эти проблемы до нас. Таким образом:
Шаблон проектирования / шаблон программирования / паттерн — это типичные способы решения часто возникающих задач в сфере разработки ПО.
ВАЖНО:
Паттерн — это не готовое решение, которое можно откуда-то скопировать и вставить в Вашу программу. Это только общие принципы, которые надо уметь правильно применить.
Мне надо знать паттерны?
- Паттерны очень часто применяются на практике. Конечно, для начинающих программистов понимание паттернов не всегда заходит легко. Так что наберитесь терпения и учим, учим, учим.
- Паттерны часто спрашивают на собеседованиях.
- И самое главное — паттерны предлагают Вам готовые решения. Они помогут Вам сохранить время и усилия, а качество программы повысится.
Откуда они взялись
Хотя сама идея паттернов далеко не новая, популярной она стала после выхода книги «Приёмы объектно-ориентированного проектирования. Паттерны проектирования«. Это произошло в 1994 году. С тех пор мир захватила «шаблономания»
Какие они бывают
Есть основные три категории паттернов:
- Порождающие (Creational Design Patterns)
Эти шаблоны что-то создают. Например, «как создать объект, который нельзя изменить»? «Как создать класс, который будет создавать новые объекты других классов?»?
- Структурные (Structural Design Patterns)
Отвечают за иерархию классов и интерфейсов. Например, «как заставить объекты с несовместимыми интерфейсами работа вместе»?
- Поведенческие (Behavioral Design Patterns)
Помогает добиться нужного поведения от объектов. Например, «как сделать так, чтобы объекты одного класса следили за изменениями в других классах и реагировали на них»?
Из чего состоит паттерн?
- Имя
- Задача, которую решает паттерн
- Решение:
- Структуры классов, составляющих решение;
- Примера на одном из языков программирования;
А конкретнее?
Существует 23 классических шаблона проектирования, с которых все и началось. В настоящий момент паттернов намного больше — минимум в 2-3 раза больше.
Здесь о каждом из них мы, конечно, говорить не будем — это много Но мы расскажем об основных паттернах в будущих статьях.
Самыми-самыми «базовыми» шаблонами проектирования можно назвать следующие:
С них можно начинать изучение паттернов. Ниже в этой статье Вы найдете ссылочки на статьи по этим паттернам.
Что следует знать
Одинаковые ли шаблоны для всех языков программирования?
Да, в целом понятия паттернов не привязано к конкретному языку. Тем не менее, когда говорят о паттернах, чаще всего имеют ввиду объектно-ориентированные языки программирования.
Паттернов очень много. Более того, кроме трех основных категорий, которые мы описали выше, есть и другие — шаблоны параллельного программирования, архитектурные шаблоны проектирования, и даже антипаттерны!
Да, конечно. Останется только всем о нем рассказать
Это все, что мы хотели Вам рассказать в данной статье.
Статьи, посвященные конкретным паттернам, Вы найдете по этим ссылочкам:
- Паттерн Singleton — Часть 1
- Паттерн Singleton — Часть 2
- Паттерн Builder
- Паттерн Factory — Часть 1
Надеемся, наша статья была Вам полезна. Также есть возможность записаться на наши курсы. Детальнее у на сайте.
- ← Правила наследования в Java
- Методы в Java →
Паттерны проектирования
Паттерны (или шаблоны) проектирования описывают типичные способы решения часто встречающихся проблем при проектировании программ.
Каталог паттернов
Список из 22-х классических паттернов, сгруппированых по предназначению.
Польза паттернов
Вы можете вполне успешно работать, не зная ни одного паттерна. Но зная паттерны, вы получаете ещё один инструмент в свой личный набор профессионала.
Классификация
Паттерны отличаются по уровню сложности, охвата и детализации проектируемой системы. Кроме этого, их можно поделить на три группы, относительно решаемых проблем.
История паттернов
Кто и когда придумал паттерны? Можно ли использовать язык паттернов вне разработки программного обеспечения?
Критика паттернов
Так ли паттерны хороши на самом деле? Всегда ли можно их использовать? Почему, иногда, паттерны бывают вредными?
Погружение в Паттерны
Электронная книга о паттернах и принципах проектирования. Доступна в PDF/EPUB/MOBI. Включает в себя архив с примерами на 9 языках программирования.
- Премиум контент
- Книга о паттернах
- Курс по рефакторингу
- Введение в рефакторинг
- Чистый код
- Технический долг
- Когда рефакторить
- Как рефакторить
- Раздувальщики
- Длинный метод
- Большой класс
- Одержимость элементарными типами
- Длинный список параметров
- Группы данных
- Операторы switch
- Временное поле
- Отказ от наследства
- Альтернативные классы с разными интерфейсами
- Расходящиеся модификации
- Стрельба дробью
- Параллельные иерархии наследования
- Комментарии
- Дублирование кода
- Ленивый класс
- Класс данных
- Мёртвый код
- Теоретическая общность
- Завистливые функции
- Неуместная близость
- Цепочка вызовов
- Посредник
- Неполнота библиотечного класса
- Составление методов
- Извлечение метода
- Встраивание метода
- Извлечение переменной
- Встраивание переменной
- Замена переменной вызовом метода
- Расщепление переменной
- Удаление присваиваний параметрам
- Замена метода объектом методов
- Замена алгоритма
- Перемещение метода
- Перемещение поля
- Извлечение класса
- Встраивание класса
- Сокрытие делегирования
- Удаление посредника
- Введение внешнего метода
- Введение локального расширения
- Самоинкапсуляция поля
- Замена простого поля объектом
- Замена значения ссылкой
- Замена ссылки значением
- Замена поля-массива объектом
- Дублирование видимых данных
- Замена однонаправленной связи двунаправленной
- Замена двунаправленной связи однонаправленной
- Замена магического числа символьной константой
- Инкапсуляция поля
- Инкапсуляция коллекции
- Замена кодирования типа классом
- Замена кодирования типа подклассами
- Замена кодирования типа состоянием/стратегией
- Замена подкласса полями
- Разбиение условного оператора
- Объединение условных операторов
- Объединение дублирующихся фрагментов в условных операторах
- Удаление управляющего флага
- Замена вложенных условных операторов граничным оператором
- Замена условного оператора полиморфизмом
- Введение Null-объекта
- Введение проверки утверждения
- Переименование метода
- Добавление параметра
- Удаление параметра
- Разделение запроса и модификатора
- Параметризация метода
- Замена параметра набором специализированных методов
- Передача всего объекта
- Замена параметра вызовом метода
- Замена параметров объектом
- Удаление сеттера
- Сокрытие метода
- Замена конструктора фабричным методом
- Замена кода ошибки исключением
- Замена исключения проверкой условия
- Подъём поля
- Подъём метода
- Подъём тела конструктора
- Спуск метода
- Спуск поля
- Извлечение подкласса
- Извлечение суперкласса
- Извлечение интерфейса
- Свёртывание иерархии
- Создание шаблонного метода
- Замена наследования делегированием
- Замена делегирования наследованием
- Введение в паттерны
- Что такое Паттерн?
- История паттернов
- Зачем знать паттерны?
- Критика паттернов
- Классификация паттернов
- Фабричный метод
- Абстрактная фабрика
- Строитель
- Прототип
- Одиночка
- Адаптер
- Мост
- Компоновщик
- Декоратор
- Фасад
- Легковес
- Заместитель
- Цепочка обязанностей
- Команда
- Итератор
- Посредник
- Снимок
- Наблюдатель
- Состояние
- Стратегия
- Шаблонный метод
- Посетитель
- C#
- C++
- Go
- Java
- PHP
- Python
- Ruby
- Rust
- Swift
- TypeScript
Структурные паттерны проектирования: для каких задач нужны, виды и примеры реализации
В этой части материала про паттерны разбираемся, что такое структурные паттерны проектирования и какие задачи они решают, а также изучаем три самых часто используемых.
Еще раз про паттерны
Паттерны проектирования — это решения распространенных проблем при разработке приложений. Также они известны как шаблоны проектирования, паттерны объектно-ориентированного программирования и design patterns. В отличие от готовых функций или библиотек, паттерн представляет собой не конкретный код, а общую концепцию решения проблемы, которую еще нужно подстроить под задачи.
Всего существует 23 классических паттерна, которые были описаны в книге «Банды четырех». В зависимости от того, какие задачи они решают, делятся на порождающие, структурные и поведенческие.
Структурные паттерны
Согласно Википедии, структурные шаблоны (structural patterns) — шаблоны проектирования, в которых рассматривается вопрос о том, как из классов и объектов образуются более крупные структуры.
Проще говоря, структурные паттерны связаны с композицией объектов или тем, как сущности могут использовать друг друга. К ним относятся:
- Facade, или Фасад
- Adapter, или Адаптер
- Decorator, или Декоратор
- Bridge, или Мост
- Composite, или Компоновщик
- Front controller, или Единая точка входа
- Flyweight, или Приспособленец, или Лекговес
- Proxy, или Заместитель
3 самых популярных структурных паттерна
По мнению разработчиков MediaSoft Facade, Adaptor и Decorator — это самые используемые структурные паттерны в разработке. Давайте разберемся, с какими задачами они помогают справляться, и посмотрим на примеры их реализации.
Facade (Фасад)
Согласно Википедии, Facade — структурный шаблон проектирования, позволяющий скрыть сложность системы путём сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы.
Проще говоря: Facade предоставляет упрощенный интерфейс для сложной системы.
Еще проще: При оплате покупки через Apple Pay вы подносите телефон к устройству и оплачиваете покупку. Кажется, что все просто. Но на самом деле внутри этого процесса происходит гораздо больше вещей. Этот упрощенный интерфейс называется фасадом.
Когда нужен: Используется в библиотеках и позволяет описать их так, чтобы пользователю не нужно было вникать в их реализацию.
Структура паттерна:
- Фасад — интерфейс для легкого доступа. Предоставляет только тот функционал, который нужен клиенту, и скрывает всё остальное.
- Сложная система из классов и методов.
Как создать:
- Создаем интерфейс фасада. В нем перечисляем нужные нам методы системы, которые предоставляет фасад.
- Создаем класс фасада, который реализует этот интерфейс.
- В методах этого класса реализуем обращения к сложной системе, которая скрывается за фасадом.
Пример реализации:
class CPU < public void execute() < . >> class Memory < public void load(long position, byte[] data) < . >> class HardDrive < public byte[] read(long lba, int size) < . >> class Computer < // Facade private CPU cpu = new CPU(); private Memory memory = new Memory(); private HardDrive hardDrive = new HardDrive(); public void startComputer() < memory.load(BOOT_ADDRESS, hardDrive.read(BOOT_SECTOR, SECTOR_SIZE)); cpu.execute(); >> class Application < // Client public static void main(String[] args) < Computer computer = new Computer(); computer.startComputer(); >>
// Создаем классы, Doors и Body, которые отвечают за конфигурацию отдельных частей автомобиля class Doors < getNumberOfDoors(type) < return type === 'truck' ? 2 : 4; >> class Body < createBody(type) < return type === 'truck'; >> // Далее создаем класс Car, который и является нашим фасадом. В методе creacteCar мы создали экземпляры классов и вызвали нужные методы у них, чтобы получить готовую конфигурацию автомобиля. Таким образом мы оградили пользователя от нужды понимания всех классов и их взаимодействия. Взамен этого предложили удобный интерфейс class Car < createCar(type) < const doors = new Doors().getNumberOfDoors(type); const body = new Body().createBody(type); return ; > > console.log(new Car().createCar('truck')); // < doors: 2, body: true >console.log(new Car().createCar('sport')); //
Adapter (Адаптер)
Согласно Википедии, Adapter — структурный шаблон проектирования, предназначенный для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс.
Проще говоря, Adapter позволяет объектам с несовместимыми интерфейсами работать вместе.
Еще проще: европейские розетки отличаются от английских, поэтому, приезжая в Лондон, туристы обязательно берут переходник, или адаптер.
Когда нужен: часто используется, если мы работаем со сторонней библиотекой или компонентом, доступ к изменениями методов которого у нас отсутствует. Или когда есть несколько сторонних систем, доступ к которым должен осуществляться единообразно через одинаковый интерфейс.
Существует два варианта этого паттерна:
- предоставляет возможность работать при помощи интерфейса/протокола, который наши объекты должны реализовать;
- дает доступ при помощи отдельного класса-адаптера, если нам необходимо внедрить более сложную логику преобразований.
Выбор той или иной реализации этого шаблона зависит от предпочтений разработчика и сложности кода, который необходимо адаптировать.
Как создать:
- Создаем класс Адаптера.
- В этом классе реализуем метод, который принимает на входе объект в незнакомом формате, а возвращает объект нужного нам формата.
- В этом методе реализуем логику преобразования объекта одного формата в другой.
Пример реализации:
interface Chief < Object makeBreakfast(); Object makeDinner(); >public class Plumber < // Adaptee public Object getScrewNut() < . >public Object getGasket() < . >> public class ChiefAdapter extends Plumber implements Chief < // Adapter public Object makeBreakfast() < return getGasket(); >public Object makeDinner() < return getScrewNut(); >> public class Client < // Client public static void eat(Object dish) < . >public static void main(String[] args) < Chief ch = new ChiefAdapter(); eat(ch.makeBreakfast()); eat(ch.makeDinner()); >>
// У нас есть класс калькулятора, который реализует единственный метод "operation" class Calculator < operation(num1, num2, operation) < switch (operation) < case 'multiplication': return num1 * num2; case 'division': return num1 / num2; default: return NaN; >> > // Допустим нам нужно создать улучшенную версию калькулятора class NewCalculator < add(num1, num2) < return num1 + num2; >div(num1, num2) < return num1 / num2; >mult(num1, num2) < return num1 * num2; >> // Но возникает проблема, что нет обратной совместимости со старым калькулятором, как раз в этом нам и поможет адаптер. Мы адаптируем новый калькулятор под функционал старого class CalculatorAdapter < constructor() < this.calculator = new NewCalculator(); >operation(num1, num2, operation) < switch (operation) < case "add": return this.calculator.add(num1, num2); case "multiplication": return this.calculator.mult(num1, num2); case "division": return this.calculator.div(num1, num2); default: return NaN; >> > // Обратите внимание, мы используем адаптер вместо старого калькулятора const calcAdapter = new CalculatorAdapter(); const sumAdapter = calcAdapter.operation(2, 2, "multiplication"); console.log(sumAdapter); // А новый функционал используем от экземпляра класса нового калькулятора const calculator = new Calculator(); const sum = calculator.mult(2, 2); console.log(sum);
Decorator (Декоратор)
Согласно Википедии, Decorator — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту.
Проще говоря, паттерн позволяет добавлять объектам новые функции с помощью обертки без создания отдельного класса.
Еще проще: пример декоратора из жизни — это подключение мышки к ноутбуку, то есть вы добавляете новую функцию устройству, не меняя его.
Когда нужен: у этого паттерна широкая область применения. Им пользуются каждый раз, когда нужно добавить логику в уже созданные объекты и библиотеки, менять которые нельзя.
Структура:
- Компонент с заданным интерфейсом
- Декоратор, который оборачивает компонент и добавляет новое поведение.
Как создать:
- Создаем интерфейс. В нем перечисляем методы библиотеки, с которой будет работать декоратор.
- Создаем класс, реализующий этот интерфейс. В нем прописываем логику обращения к объекту, который скрывается за декоратором, и добавляем свою кастомную логику.
Пример реализации:
public interface InterfaceComponent < void doOperation(); >class MainComponent implements InterfaceComponent < public void doOperation() < System.out.print("World!"); >> abstract class Decorator implements InterfaceComponent < protected InterfaceComponent component; public Decorator (InterfaceComponent c) < component = c; >public void doOperation() < component.doOperation(); >public void newOperation() < System.out.println("Do Nothing"); >> class DecoratorComma extends Decorator < public DecoratorComma(InterfaceComponent c) < super(c); >public void doOperation() < System.out.print(","); super.doOperation(); >public void newOperation() < System.out.println("New comma operation"); >> class DecoratorHello extends Decorator < public DecoratorHello(InterfaceComponent c) < super(c); >public void doOperation() < System.out.print("Hello"); super.doOperation(); >public void newOperation() < System.out.println("New hello operation"); >> class Main < public static void main (String. s) < Decorator c = new DecoratorHello(new DecoratorComma(new MainComponent())); c.doOperation(); // Результат выполнения программы "Hello,World!" c.newOperation(); // New hello operation >>
// создаем класс, с набором свойств и методов class Car < constructor(cost) < this.cost = cost; >getCost() < return `Стоимость автомобиля: $`; > > // далее создаем декоратор, который принимает экземпляр класса // и расширяет его, в данном случае добавляем свойство color const colorCar = (car, color) => < car.color = color; return car; >const car = new Car(16000); // оборачиваем инстанс car в декоратор, и получаем расширенный класс colorCar(car, 'orange') console.log(car); // Car < cost: 16000, color: 'orange' >console.log(car.getCost()); // Стоимость автомобиля: 16000
Заключение
Паттерны проектирования — это решения распространенных проблем при разработке кода. Их знание и использование позволяет экономить время, используя готовые решения, стандартизировать код и повысить общий словарь.
В зависимости от того, какие задачи решают паттерны проектирования, они делятся на три вида: порождающие, структурные и поведенческие.
Структурные паттерны связаны с композицией объектов или тем, как сущности могут использовать друг друга. Три самых популярных из них:
Facade, или Фасад, предоставляет упрощенный интерфейс для сложной системы. Используется в библиотеках и позволяет описать их так, чтобы пользователю не нужно было вникать в их реализацию.
Adapter, или Адаптер позволяет объектам с несовместимыми интерфейсами работать вместе. Используется, если мы работаем со сторонней библиотекой или компонентом, доступ к изменениями методов которого у нас отсутствует.
Decorator, или Декоратор, позволяет добавлять объектам новые функции с помощью обертки без создания отдельного класса. Им пользуются каждый раз, когда нужно добавить логику в уже созданные объекты и библиотеки, менять которые нельзя.
В следующих статьях мы подробнее расскажем про поведенческие паттерны и разберем самые популярные из них.
Шпаргалка по шаблонам проектирования
Перевод pdf файла с сайта http://www.mcdonaldland.info/ с описанием 23-х шаблонов проектирования GOF . Каждый пункт содержит [очень] короткое описание паттерна и UML-диаграмму. Сама шпаргалка доступна в pdf, в виде двух png файлов (как в оригинале), и в виде 23-х отдельных частей изображений. Для самых нетерпеливых — все файлы в конце статьи.
Под катом — много картинок.
Условные обозначения
Отношения между классами
-
— агрегация (aggregation) — описывает связь «часть»–«целое», в котором «часть» может существовать отдельно от «целого». Ромб указывается со стороны «целого».
— композиция (composition) — подвид агрегации, в которой «части» не могут существовать отдельно от «целого».
— зависимость (dependency) — изменение в одной сущности (независимой) может влиять на состояние или поведение другой сущности (зависимой). Со стороны стрелки указывается независимая сущность.
— обобщение (generalization) — отношение наследования или реализации интерфейса. Со стороны стрелки находится суперкласс или интерфейс.
Виды паттернов
-
— поведенческие (behavioral);
— порождающие (creational);
— структурные (structural).
Список шаблонов
Хранитель (memento)
Цепочка обязанностей (chain of responsibility)
Наблюдатель (observer)
Команда (command)
Состояние (state)
Интерпретатор (interpreter)
Стратегия (strategy)
Итератор (iterator)
Шаблонный метод (template method)
Посредник (mediator)
Посетитель (visitor)
Адаптер (adapter)
Прокси (proxy)
Мост (bridge)
Абстрактная фабрика (abstract factory)
Компоновщик (composite)
Строитель (builder)
Декоратор (decorator)
Фабричный метод (factory method)
Фасад (facade)
Прототип (prototype)
Приспособленец (flyweight)
Одиночка (singleton)
Файлы
- все паттерны в pdf-файле.
- то же самое, но в png — 1 и 2 части.
- архив с нарезанными изображениями.
Upd. оригинальный pdf и изображения (1, 2).
P.S. По запросу «шаблоны проектирования» 636 топиков, а хаба нет; а по «bitcoin» — 278 топиков и хаб есть. Прошу восстановить справедливость!