ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 04.02.2024
Просмотров: 51
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
Тема 3.2. Основные шаблоны
Паттерны дают не конкретное решение, а некий путь к решению
Выбор правильного паттерна - задача нетривиальная, предполагающая от архитектора наличие интуиции, опыта, определенного творчества.
Классификация паттернов
Часто под паттернами проектирования подразумевают все виды паттернов программной индустрии, что является не совсем корректным.
В области разработки программных систем существует множество паттернов, которые отличаются областью применения, масштабом, содержимым, стилем описания.
Классификация паттернов
Например, в зависимости от сферы применения существуют такие паттерны как паттерны анализа, проектирования, тестирования, документирования, организации процесса разработки, планирования проектов и другие.
В настоящее время наиболее популярными паттернами являются паттерны проектирования.
Классификация паттернов
Одной из распространенных классификаций таких паттернов является классификация по степени детализации и уровню абстракции рассматриваемых систем.
Паттерны делятся на следующие категории:
Архитектурные паттерны
Паттерны проектирования
Идиомы
Архитектурные паттерны, являясь наиболее высокоуровневыми паттернами, описывают структурную схему программной системы в целом.
В данной схеме указываются отдельные функциональные составляющие системы, называемые подсистемами, а также взаимоотношения между ними.
Примером архитектурного паттерна является хорошо известная программная парадигма "модель-представление-контроллер" (model-view-controller - MVC).
В свою очередь, подсистемы могут состоять из архитектурных единиц уровнем ниже.
Паттерны проектирования описывают схемы детализации программных подсистем и отношений между ними, при этом они не влияют на структуру программной системы в целом и сохраняют независимость от реализации языка программирования.
Под паттернами проектирования объектно-ориентированных систем понимается описание взаимодействия объектов и классов, адаптированных для решения общей задачи проектирования в конкретном контексте.
В русскоязычной литературе обычно встречаются несколько вариантов перевода оригинального названия design patterns - паттерны проектирования, шаблоны проектирования, образцы.
Идиомы, являясь низкоуровневыми паттернами, имеют дело с вопросами реализации какой-либо проблемы с учетом особенностей данного языка программирования.
При этом часто одни и те же идиомы для разных языков программирования выглядят по-разному или не имеют смысла вовсе.
Например, в C++ для устранения возможных утечек памяти могут использоваться интеллектуальные указатели.
Интеллектуальный указатель содержит указатель на участок динамически выделенной памяти, который будет автоматически освобожден при выходе из зоны видимости.
В среде Java такой проблемы просто не существует, так как там используется автоматическая сборка мусора.
Обычно, для использования идиом нужно глубоко знать особенности применяемого языка программирования.
П программной области существуют и другие виды паттернов, не относящиеся к проектированию вообще, например, паттерны анализа, тестирования, документирования и др.
Шаблоны бывают следующих трех видов:
- Порождающие.
Структурные.
Поведенческие.
Если говорить простыми словами, то это шаблоны, которые предназначены для создания экземпляра объекта или группы связанных объектов.
Паттерны классов описывают отношения между классами посредством наследования.
Отношения между классами определяются на стадии компиляции.
К таким паттернам относятся:
- Фабричный метод (Factory Method)
Интерпретатор (Interpreter)
Шаблонный метод (Template Method)
Адаптер (Adapter)
Другая часть паттернов - паттерны объектов описывают отношения между объектами.
Эти отношения возникают на этапе выполнения, поэтому обладают большей гибкостью.
К паттернам объектов относят следующие:
- Абстрактная фабрика (Abstract Factory)
Строитель (Builder)
Прототип (Prototype)
Одиночка (Singleton)
Мост (Bridge)
Компоновщик (Composite)
Декоратор (Decorator)
Фасад (Facade)
Приспособленец (Flyweight)
Заместитель (Proxy)
Цепочка обязанностей (Chain of responsibility)
Команда (Command)
Итератор (Iterator)
Посредник (Mediator)
Хранитель (Memento)
Наблюдатель (Observer)
Состояние (State)
Стратегия (Strategy)
Посетитель (Visitor)
Прежде всего при решении какой-нибудь проблемы надо выделить все используемые сущности и связи между ними и абстрагировать их от конкретной ситуации.
Затем надо посмотреть, вписывается ли абстрактная форма решения задачи в определенный паттерн.
Например, суть решаемой задачи может состоять в создании новых объектов.
В этом случае, возможно, стоит посмотреть на порождающие паттерны.
Причем лучше не сразу взять какой-то определенный паттерн - первый, который показался нужным, а посмотреть на несколько родственных паттернов из одной группы, которые решают одну и ту же задачу.
Один паттерн может иметь различные реализации, и чем чаще вы будете сталкиваться с этими реализациями, тем лучше вы будете понимать смысл паттерна.
Не стоит использовать паттерн, если вы его не понимаете, даже если он на первый взгляд поможет вам в решении задачи.
И в конечном счете надо придерживаться принципа KISS (Keep It Simple, Stupid) - сохранять код программы по возможности простым и ясным. Ведь смысл паттернов не в усложнении кода программы, а наоборот в его упрощении.
Прежде чем приступить к изучению основных паттернов рассмотрим основные отношения между объектами, которые помогут нам понять связи между сущностями при их использовании в паттернах.
Можно выделить несколько основных отношений:
- наследование, реализация, ассоциация, композиция, агрегация.
Наследование
Наследование является базовым принципом ООП и позволяет одному классу (наследнику) унаследовать функционал другого класса (родительского).
Нередко отношения наследования еще называют генерализацией или обобщением.
Наследование определяет отношение IS A, то есть "является".
В данном случае используется наследование, а объекты класса Manager также являются и объектами класса User.
С помощью диаграмм UML отношение между классами выражается в незакрашенной стрелочке от класса-наследника к классу-родителю:
Реализация
Реализация предполагает определение интерфейса и его реализация в классах.
Реализация
Например, имеется интерфейс IMovable с методом Move, который реализуется в классе Car:
С помощью диаграмм UML отношение реализации также выражается в незакрашенной стрелочке от класса к интерфейсу, только линия теперь пунктирная:
Ассоциация
Ассоциация - это отношение, при котором объекты одного типа неким образом связаны с объектами другого типа.
Ассоциация
Например, объект одного типа содержит или использует объект другого типа. Например, игрок играет в определенной команде:
Класс Player связан отношением ассоциации с класом Team.
На схемах UML ассоциация обозначается в виде обычно стрелки:
Нередко при отношении ассоциации указывается кратность связей.
В данном случае единица у Team и звездочка у Player на диаграмме отражает связь 1 ко многим.
То есть одна команда будет соответствовать многим игрокам.
Агрегация и композиция являются частными случаями ассоциации.
Композиция
Композиция определяет отношение HAS A, то есть отношение "имеет".
Композиция
Например, в класс автомобиля содержит объект класса электрического двигателя:
При этом класс автомобиля полностью управляет жизненным циклом объекта двигателя.
При уничтожении объекта автомобиля в области памяти вместе с ним будет уничтожен и объект двигателя. И в этом плане объект автомобиля является главным, а объект двигателя - зависимой.
На диаграммах UML отношение композиции проявляется в обычной стрелке от главной сущности к зависимой, при этом со стороны главной сущности, которая содержит, объект второй сущности, располагается закрашенный ромбик:
Агрегация
От композиции следует отличать агрегацию.
Она также предполагает отношение HAS A, но реализуется она иначе:
При агрегации реализуется слабая связь, то есть в данном случае объекты Car и Engine будут равноправны.
В конструктор Car передается ссылка на уже имеющийся объект Engine.
И, как правило, определяется ссылка не на конкретный класс, а на абстрактный класс или интерфейс, что увеличивает гибкость программы.
Отношение агрегации на диаграммах UML отображается также, как и отношение композиции, только теперь ромбик будет незакрашенным:
Общие рекомендации
Вместо наследования следует предпочитать композицию.
При наследовании весь функционал класса-наследника жестко определен на этапе компиляции.
Во время выполнения программы его нельзя динамически переопределить.
Класс-наследник не всегда может переопределить код, который определен в родительском классе.
Композиция позволяет динамически определять поведение объекта во время выполнения, и поэтому является более гибкой.
Общие рекомендации
Вместо композиции следует предпочитать агрегацию, как более гибкий способ связи компонентов.
Не всегда агрегация уместна.
Например, есть класс человека, который содержит объект нервной системы. Понятно, что в реальности, по крайней мере на текущий момент, невозможно вовне определить нервную систему и внедрить ее в человека. То есть в данном случае человек будет главным компонентом, а нервная система - зависимым, подчиненным, и их создание и жизненный цикл будет происходить совместно, поэтому здесь лучше выбрать композицию.
Интерфейсы или абстрактные классы
Один из принципов проектирования гласит, что при создании системы классов надо программировать на уровне интерфейсов, а не их конкретных реализаций.
Под интерфейсами в данном случае понимаются не только типы C#, определенные с помощью ключевого слова interface, а определение функционала без его конкретной реализации.
Под данное определение попадают как интерфейсы, так и абстрактные классы, которые могут иметь абстрактные методы без конкретной реализации.
При проектировании программ в паттернах можно заменять абстрактные классы на интерфейсы и наоборот.
Когда следует использовать абстрактные классы:
- Если надо определить общий функционал для родственных объектов
Если мы проектируем довольно большую функциональную единицу, которая содержит много базового функционал
Если нужно, чтобы все производные классы на всех уровнях наследования имели некоторую общую реализацию. При использовании абстрактных классов, если мы захотим изменить базовый функционал во всех наследниках, то достаточно поменять его в абстрактном базовом классе.
Если же нам вдруг надо будет поменять название или параметры метода интерфейса, то придется вносить изменения и также во всех классы, которые данный интерфейс реализуют.
Когда следует использовать интерфейсы:
- Если нам надо определить функционал для группы разрозненных объектов, которые могут быть никак не связаны между собой.
Если мы проектируем небольшой функциональный тип
Ключевыми здесь являются первые пункты, которые можно свести к следующему принципу: если классы относятся к единой системе классификации, то выбирается абстрактный класс. Иначе выбирается интерфейс.