Сдам Сам

ПОЛЕЗНОЕ


КАТЕГОРИИ







Механизмы повторного использования





Два наиболее распространенных способа повторного использования функциональности в ОО системах – это наследование класса и композиция объектов. Как уже говорилось, наследование класса позволяет определить реализацию одного класса в терминах другого. Повторное использование за счет порождения подкласса часто называют “прозрачным ящиком” (white-box reuse). Такой термин подчеркивает, что внутреннее устройство родительских классов видимо подклассам.

Второй способ повторного использования некоторой функциональности – композиция объектов. В этом случае новую, более сложную функциональность мы получаем путем установки между объектами отношений связи или агрегации. Для композиции требуется, что бы объекты имели четко определенные интерфейсы. Такой способ повторного использования называют “черным ящиком” (black-box reuse).

И у наследования, и у композиции есть достоинства и недостатки. Наследование определяется статически, на этапе компиляции, его проще использовать, поскольку оно напрямую поддержано языками программирования. Из-за этого наследование быстро работает и быстро пишется. С другой стороны, при наследовании нельзя заменить унаследованную от родителя реализацию во время исполнения программы. Кроме того, родительский класс нередко хотя бы частично определяет физическое представление своих подклассов и часто бывает, что изменение в родителе приводит к каскадному изменению всех, или большинства, подклассов. Наконец при наследовании подклассы имеют доступ к полям родителя, что нарушает принцип инкапсуляции. Перечисленные недостатки наследования, в особенности главное из них – зависимость подклассов от реализации суперкласса, могут повлечь за собой проблемы при попытке повторного использования классов. Если хотя бы один аспект класса оказался непригоден для новой деловой среды, приходится переписывать родительский класс, или вообще программировать новую иерархию наследования. С проблемой можно справиться, если наследовать только абстрактным классам, поскольку в них обычно нет реализации или она минимальна.

Композиция объектов определяется динамически во время выполнения за счет того, что объекты пользуют ссылки на другие объекты. Композицию можно применить, если объекты соблюдают интерфейсы друг друга. Для этого, в свою очередь, требуется тщательно проектировать интерфейсы, так что бы один объект можно было использовать с широким спектром других. Но и выигрыш велик. Поскольку доступ к объектам осуществляется через их интерфейсы, мы не нарушаем инкапсуляцию. Во время выполнения программы любой объект можно заменить другим, лишь бы он имел тот же интерфейс.

Проектирование ОО программ – дело само по себе трудной, а если их нужно использовать повторно, то все становится еще сложнее. Необходимо подобрать подходящие объекты, отнести их к различным классам, соблюдая разумную степень детализации, определить интерфейсы классов и иерархию наследования, и установить существенные отношения между классами. Дизайн должен, с одной стороны, соответствовать решаемой задаче, с другой – быть общим, чтобы удалось учесть все требования, которые могут возникнуть в будущем. Хотелось бы избежать вовсе или, по крайней мере, свести к минимуму, необходимость перепроектирования. Поднаторевшие в ОО проектировании разработчики скажут Вам, что обеспечить “правильный”, то есть в остаточной мере гибкий и пригодный для повторного использования дизайн, с первого раза очень трудно, если вообще возможно. Прежде чем считать цель достигнутой, они обычно пытаются опробовать найденное решение на нескольких задачах, каждый раз модифицируют его. Создать лучший дизайн “на коленках”, то есть сразу и без особых усилий, удается лишь гениальным людям или настоящим профи. С другой стороны, новички, пытающиеся применять приемы ОО проектирования, часто испытывают шок от количества возможных вариантов и возвращаются к привычным не ОО методикам. Проходит немало времени, прежде чем приходит понимание того, что же такое удачный ОО дизайн. Опытные проектировщики, очевидно, знают какие то тонкости, ускользающие от новичков. Попытаемся ответить на вопрос: “А что же это за тонкости?”.



Прежде всего, опытному разработчику понятно, что не нужно решать каждую задачу с нуля. Вместо этого он пытается повторно воспользоваться решениями, которые оказались удачными в прошлом. Отыскав хорошее решение один раз, он будет прибегать к нему снова и снова. Часто, именно благодаря накопленному опыту, проектировщик становится экспертом. Естественно, бесценный опыт получается не только своими шишками. Благо многие гуру OOD делятся своими секретами. Их решения накапливаются, систематизируются и используются проектировщиками во всем мире для того, что бы дизайны получались гибкими, модифицируемыми и расширяемыми. Такие решения и называются паттернами проектирования.

По словам Кристофера Александра, “любой паттерн описывает задачу, которая снова и снова возникает в нашей работе, а так же принцип ее решения, причем таким образом, что это решение можно потом использовать миллион раз, ничего не изобретая заново”. И хотя Александр имел в виду возникающие при проектировании зданий и городов, его слова верны и в отношении паттернов OOD. Наши решения выражаются в терминах объектов и интерфейсов, а не стен, дверей и оконных рам, но в обоих случаях смысл паттерна- предложить решение определенной задачи в конкретном контексте.

 

В общем случае паттерн состоит из четырех основных элементов:

 

1. Имя. Сославшись на него, мы можем сразу описать проблему проектирования, ее решения и их последствия. Присваивание паттернам имен позволяет проектировать на более высоком уровне абстракции. С помощью имен паттернов можно вести общение с коллегами, например, я могу сказать: “Для создания объектов в данном случае предлагаю использовать фабричный метод”. Или, если позволить себе некоторую стилевую вольность, ”Здесь используем принцип Голливуда”. Короче говоря, назначение паттернов имен упрощает общение в профессиональной среде.

2. Задача. Задача это описание того, когда следует применять паттерн. Необходимо сформулировать задачу и ее контекст. Может описываться конкретная проблема проектирования, например способ представления алгоритмов в виде объектов. Так же задача может включать перечень условий, при выполнении которых имеет смысл применять данный паттерн.

3. Решение. Решение представляет собой описание элементов дизайна, отношений между ними, функций каждого элемента. Конкретный дизайн или реализация не имеются ввиду, поскольку паттерн – это шаблон, применимый в самых разных ситуациях. Просто дается абстрактное описание задачи проектирования и того, как она может быть решена с помощью некоего весьма обобщенного сочетания элементов (в нашем случае под элементами понимаются классы и объекты).

4. Результаты. Результаты это следствия применения паттерна и разного рода компромиссы. Хотя при описании проектных решений о последствиях часто не упоминают, знать о них необходимо, чтобы можно было выбрать между различными вариантами и оценить преимущества и недостатки данного паттерна.

 

В ряде случаев при описании паттернов задаются дополнительные элементы. О них поговорим при рассмотрении конкретных паттернов. Сейчас же мне хотелось бы немного поговорить о том, как решаются задачи проектирования с помощью паттернов.

Порождающие паттерны

Порождающие паттерны – паттерны предназначенные для создания объектов. Эти паттерны оказываются важны, когда система больше зависит от композиции объектов, чем от наследования классов. Получается так, что основной упор делается не на жестком кодировании фиксированного набора поведений, а на определении небольшого набора фундаментальных поведений, с помощью композиции которых можно получать любое число более сложных.

Для порождающих паттернов актуальны две темы. Во-первых, эти паттерны инкапсулируют знания о конкретных классах, которые применяются в системе. Во-вторых, скрывают детали того, как эти классы создаются и стыкуются. Единственная информация об объектах, известная системе, - это их интерфейсы, определенные с помощью абстрактных классов. Следовательно, порождающие паттерны обеспечивают большую гибкость при решении вопроса о том, что создается, то это создает, как и когда. Можно собрать систему из «готовых» объектов с самой различной структурой и функциональностью статически (на этапе компиляции) или динамически (во время выполнения). Иногда допустимо выбирать между тем или иным порождающим паттерном. Например, есть случаи, когда с пользой для дела можно использовать как прототип, так и абстрактную фабрику. В других ситуациях порождающие паттерны дополняют друг друга.

Паттерн Singleton

Название

Singleton (одиночка).

Задача

Для некоторых классов важно, чтобы существовал только один экземпляр. Хотя в системе может быть много принтеров, но возможен лишь один спулер. Должна быть только одна файловая система и единственная оконный менеджер. В ряде случае бухгалтерская система должна обслуживать только одну компания.

Как гарантировать, что у класса есть единственный экземпляр и что этот экземпляр легко доступен? Глобальная переменная дает доступ к объекту, но нее запрещает создания нескольких объектов класса. Более удачное решение – сам класс контролирует то, что у него есть только один экземпляр, может запретить создание дополнительных экземпляров, перехватывая запросы на создание новых объектов, и он же способен предоставить доступ к своему экземпляру. Это и есть назначение паттерна одиночка.

Из всего вышесказанного можно сформулировать задачу паттерна Singleton. Singleton гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.

Структура

 

Рисунок 2.1 Структура паттерна Singleton

Отношения

Клиенты получают доступ к экземпляру Singleton только через Singelton::getInstance()

Результаты

  • Гарантирует наличие в системе только одного экземпляра для некоторого класса.
  • Из-за того, что в языках программирования подобное поведение реализуется через static методы, нельзя использовать полиморфизм.

Паттерн Prototype

Название

Prototype (прототип).

Назначение

Задает виды создаваемых объектов с помощью экземпляра-прототипа и создает новые объекты путем копирования этого прототипа.

Задача

Паттерн Prototype обеспечивает создание копии объекта без задания конкретного класса со стороны клиента. Используйте паттерн Прототип, когда существует необходимость создавать копии объектов не зная их конкретных типов, а зная только лишь базовый абстрактный тип.

Структура

Рисунок 2.2 Структура паттерна Prototype

  • Prototype-Прототип: - объявляет интерфейс для клонирования самого себя;
  • СonсretePrototype – конкретный прототип: - реализует операцию клонирования себя;
  • Client -клиент: - создает новый объект, обращаясь к прототипу с запросом клонировать себя.

 

Результаты

Преимущества применения прототипа таковы: во-первых, прототип позволяет получить новые объекты не задавая их конкретные классы; во-вторых, мы можем добавлять в систему новые классы за частую не изменяя клиента; в-третьих, мы скрываем от клиента названия конкретных классов (клиент знает только о классе Prototype) уменьшая тем самым степень связности клиента (то есть уменьшаем количество известных ему элементолв)

Среди недостатков можно отметить необходимость добавления в каждый конкретный класс реализации метода clone(), что может быть не всегда удобно.

Паттерн Factory method

Название

Factory method (фабричный метод), Virtual constructor (виртуальный конструктор).

Назначение

Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс необходимо создать.

Мотивация

Каркасы пользуются абстрактными классами для определения и поддержания отношений между объектами. Кроме того, каркас часто отвечает за создание самих объектов.

Рассмотрим каркас для приложений, способных представлять пользователю сразу несколько документов. Две основных абстракции в таком каркасе - это классы Application и Document. Оба класса абстрактные, поэтому клиенты должны порождать от них подклассы для создания специфичных для приложения реализаций. Например, чтобы создать приложение для рисования, мы определим классы DrawingApplication и DrawingDocument. Класс Application отвечает за управление документами и создает их по мере необходимости, допустим, когда пользователь выбирает из меню пункт Open (открыть) или New (создать).

 

Рисунок 2.3 Структура паттерна Factory Method (пример)

Поскольку решение о том, какой подкласс класса Document необходимо создать зависит от приложения, то Application не может «предсказать», что именно понадобится. Этому классу известно лишь, когда нужно создавать новый документ, а не какой документ создать. Возникает дилемма: каркас должен создавать экземпляры классов, но знает он лишь об абстрактных классах, экземпляры которых создавать нельзя.

Решение предлагает паттерн фабричный метод. В нем инкапсулируется информация о том, какой подкласс класса Document создать, и это знание выводится за пределы каркаса.

Подклассы класса Application переопределяют абстрактную операцию CreateDocument таким образом, чтобы она возвращала подходящий подкласс класса Document. Как только подкласс Application создан, он может создавать специфические для приложения документы, ничего не зная об их классах. Операцию CreateDocument мы называем фабричным методом, поскольку она отвечает за «изготовление» объекта.

Применимость

Классу заранее неизвестно, объекты каких классов ему нужно создавать; класс спроектирован так, чтобы объекты, которые он создает, специфицировались подклассами; класс делегирует свои обязанности одному из нескольких вспомогательных подклассов, и вы планируете локализовать знание о том, какой класс принимает эти обязанности на себя.

Структура и участники

Структура паттерна представлена на рис. 2.4

  • Product (Document) - Продукт - определяет интерфейс объектов, создаваемых фабричным методом;
  • ConcreteProduct (MyDocument) - конкретный продукт:- реализует интерфейс Product;
  • Creator (Application) – создатель - объявляет фабричный метод, возвращающий объект типа Product. Creator может также определять реализацию по умолчанию фабричного метода.
  • ConcreteCreator (MyApplication) - конкретный создатель - замещает фабричный метод, возвращающий объект Concrete Product.

Отношения

Создатель "полагается" на свои подклассы в определении фабричного метода, который будет возвращать экземпляр подходящего конкретного продукта.

Результаты

Фабричные методы избавляют проектировщика от необходимости встраивать в код зависящие от приложения классы. Код имеет дело только с интерфейсом класса Product, поэтому он может работать с любыми определенными пользователями классами конкретных продуктов. Потенциальный недостаток фабричного метода состоит в том, что клиентам, возможно, придется создавать подкласс класса Creator для создания лишь одного объекта ConcreteProduct. Порождение подклассов оправдано, если клиенту так или иначе приходится создавать подклассы Creator, в противном случае клиенту придется иметь дело с дополнительным уровнем подклассов.

Рисунок 2.4 Структура паттерна Factory Method

Паттерн Reflection

Название

Reflection

Назначение

Обеспечить возможность создание объектов по имени класса для языков программирования, не поддерживающих рефлексию явно.

Структура и участники

Рисунок 2.5 Структура паттерна Рефлексия

  • Object. Базовый класс для всех классов системы, для которых необходимо создание объектов по имени. В простейшем случае объявляет интерфейс с одним абстрактным методом createInstance().
  • ConcreteClass1. Конкретный подкласс Object. Реализует метод createInstance(). Наиболее очевидной реализацией этого метода в конкретных подклассах Object является создание самого себя .
  • ObjectFactory. Фабрика объектов. Реализуется в соответствии с шаблоном Singleton. Данный класс предоставляет интерфейс для регистрации классов (registerClass()) и для создания объектов (createObjectByName())
  • Library. Предоставляет абстрактный интерфейс для модулей системы. Конкретными подклассами Library могут являться классы, инкапсулирующие загрузку динамических библиотек, таких как Dynamic Load Library (DLL) в Win32-системах или Shared Library в Unix-системах. В задачу данного класса входит регистрация содержащихся в них классов у ObjectFactory (рис. 2.6)
  • Client. Описывает классы, объекты которых являются клиентами относительно ObjectFactory. Клиенты используют ObjectFactory для создания объектов вызывая у последней createObjectByName() (рис. 2.7)

 

Взаимодействие

Рисунок 2.6 Регистрация класса

Рисунок 2.7 Создание объекта

Результаты

Основным результатом применения данного паттерна являются возможность создавать объекты классов по имени что, в свою, очередь, повышает гибкость программного решения. С другой стороны, паттерн подразумевает организацию программы как набора динамически подгружаемых библиотек, что может оказаться в некоторых случаях неприемлемым.

Паттерн Creator

Название

Creator (Создатель)

Назначение

Определяет, кто должен отвечать за создание объектов.

Решение

Назначить классу В обязанность создавать экземпляры класса А если выполняется одно из следующих условий.

  • Класс В агрегирует (aggregate) объекты А.
  • Класс В активно использует (closely uses) объекты А.
  • Класс В обладает данными инициализаци, которые будут передаваться объектам А при их создании.

Результаты

Поддерживается шаблон Low Coupling, способствующий снижению затрат на сопровождение и обеспечивающий возможность повторного использования созданных компонентов в дальнейшем.

Структурные паттерны

В структурных паттернах рассматривается вопрос о том, как из классов и объектов образуются более крупные структуры. Структурные паттерны уровня класса используют наследование для составления композиций из интерфейсов и реализаций.

Паттерн Adapter

Название

Adapter (адаптер), Wrapper (обертка)

Задача

Иногда класс из инструментальной библиотеки не удается использовать только потому, что его интерфейс не соответствует создаваемому приложению. В некоторых случаях мы можем поменять интерфейс на нужный, но если у нас нет исходного кода библиотеки такое решение не подходит. Еще один вариант - определить другой класс таким образом, что от будет адаптировать интерфейс одного класса к другому интерфейсу.

Паттерн Adapter преобразует интерфейс одного класса в интерфейс другого, который ожидают клиенты. Адаптер обеспечивает совместную работу классов с несовместимыми интерфейсами, которая без него была бы невозможна.

Результаты

Первый вариант паттерна называется адаптером объектов, второй адаптером классов. Результаты применения адаптеров объектов и классов немного различаются.

Адаптер классов

  • Адаптирует Adaptee к Target, перепоручая действия конкретному классу Adaptee. Так как при наследовании структура программы остается жесткой, данный паттерн не будет работать, если мы захотим одновременно адаптировать класс и его подклассы.
  • Так как Adapter является подклассом Adaptee, мы можем легко заменить некоторые операции Adaptee.
  • Вводится только один новый объект. Что бы добраться до адаптируемого класса не нужно никаких дополнительных обращений по указателю.

Адаптер объектов

  • Позволяет одному адаптеру работать со многими адаптируемыми объектами (а самим Adaptee и со всеми его подклассами).
  • Затрудняет замещение операций Adaptee (так как Adapter не является подклассом Adaptee, мы можем использовать полиморфизм, только породив от Adaptee подкласс, заменив в нем операции Adaptee и заставив Adapter ссылаться на этот класс)

 

Структура

Рисунок 2.8 Структура паттерна Adapter (адаптер объектов)

 

 

Рисунок 2.9 Структура паттерна Adapter (адаптер классов)

 

Паттерн Composite

Название

Composite (компоновщик)

Мотивация

Такие приложения, как графические редакторы и редакторы электрических (схем, позволяют пользователям строить сложные диаграммы из более простых компонентов.

 

Рисунок 2.10 Структура паттерна Composite (пример)

Проектировщик может сгруппировать мелкие компоненты для формирования более крупных, которые, в свою очередь, могут стать основой для создания еще более крупных. В простой реализации допустимо было бы определить классы графических примитивов, например текста и линий, а также классы, выступающие в роли контейнеров для этих примитивов. Но у такого решения есть существенный недостаток. Программа, в которой эти классы используются, должна по-разному обращаться с примитивами и контейнерами, хотя пользователь чаще всего работает с ними единообразно. Необходимость различать эти объекты усложняет приложение. Паттерн компоновщик описывает, как можно применить рекурсивную композицию таким образом, что клиенту не придется проводить различие между простыми и составными объектами. Пример применения шаблона можно увидеть на на рис. 2.10.

Ключом к паттерну компоновщик является абстрактный класс, который представляет одновременно и примитивы, и контейнеры. В графической системе этот класс может называться Graphic. В нем объявлены операции, специфичные. Для каждого вида графического объекта (такие как Draw) и общие для всех составных объектов, например операции для доступа и управления потомками. Подклассы Line, Rectangle и Text (см. диаграмму выше) определяют примитивные графические объекты. В них операция Draw реализована соответственно для рисования прямых, прямоугольников и текста. Поскольку у примитивных объектов нет потомков, то ни один из этих подклассов не реализует операции, относящиеся к управлению потомками. Класс Picture определяет агрегат, состоящий из объектов Graphic. Реализованная в нем операция Draw вызывает одноименную функцию для каждого Потомка, а операции для работы с потомками уже не пусты. Поскольку интерфейс Класса Picture соответствует интерфейсу Graphic, то в состав объекта Picture Могут входить и другие такие же объекты. Ниже на диаграмме «оказана типичная структура составного объекта, рекурсивно скомпонованного из объектов класса Graphic.

Таким образом можно сформулировать задачу, для которой предназначен паттерн. Компоновщик компонует объекты в древовидные структуры для представления иерархии часть-целое. Позволяет клиентам единообразно трактовать индивидуальные и составные объекты.

Структура

Рисунок 2.11 Структура паттерна Composite

Результаты

  • определяет иерархии классов, состоящие из примитивных и составных объектов. Из примитивных объектов можно составлять более сложные, которые, в свою очередь, участвуют в более сложных композициях и так далее. Любой клиент, ожидающий примитивного объекта, может работать и с составным;
  • упрощает архитектуру клиента. Клиенты могут единообразно работать с индивидуальными и объектами и с составными структурами. Обычно клиенту неизвестно, взаимодействует ли он с листовым или составным объектом. Это упрощает код клиента, поскольку нет необходимости писать функции, ветвящиеся в зависимости от того, с объектом какого класса они работают;
  • облегчает добавление новых видов компонентов. Новые подклассы классов Composite или Leaf будут автоматически работать с уже существующими структурами и клиентским кодом. Изменять клиента при добавлении новых компонентов не нужно;
  • способствует созданию общего дизайна. Однако такая простота добавления новых компонентов имеет и свои отрицательные стороны; становится трудно наложить ограничения на то, какие объекты могут входить в состав композиции. Иногда желательно, чтобы составной объект мог включать только определенные виды компонентов. Паттерн компоновщик не позволяет воспользоваться для реализации таких ограничений статической системой типов. Вместо этого следует проводить проверки во время выполнения.

Паттерн Decorator

Название

Decorator (декоратор)

Задачи

Иногда бывает нужно возложить дополнительные обязанности на отдельный объект, а не на класс в целом. Так, библиотека для построения графических интерфейсов пользователя должна «уметь» добавлять новое свойство, скажем, рамку или новое поведение (например, возможность прокрутки к любому элемент у интерфейса). Добавить новые обязанности допустимо с помощью наследования. При наследовании классу с рамкой вокруг каждого экземпляра подкласса будет рисоваться рамка. Однако это решение статическое, а значит, недостаточно гибкое. Клиент не может управлять оформлением компонента рамкой. Более гибким является другой подход: поместить компонент в другой объект, называемый декоратором, который как раз и добавляет рамку. Декоратор следует интерфейсу декорируемого объекта, поэтому его присутствие прозрачно для клиентов компонента. Декоратор переадресует запросы внутреннему компоненту, но может выполнять и дополнительные действия (например, рисовать рамку) до или после переадресации. Поскольку декораторы прозрачны, они могут вкладываться друг в друга, добавляя тем самым любое число новых обязанностей.

 

Рисунок 2.12 Структура паттерна Decorator (пример)

Предположим, что имеется объект класса TextView, который отображает текст в окне. По умолчанию TextView не имеет полос прокрутки, поскольку они не всегда нужны. Но при необходимости их удастся добавить с помощью декоратора ScrollDecorator. Допустим, что еще мы хотим добавить жирную сплошную рамку вокруг объекта TextView. Здесь может помочь декоратор BorderDecorator. Ниже на диаграмме показано, как композиция объекта TextView с объектами BorderDecorator и ScrollDecorator порождает элемент для ввода текста, окруженный рамкой и снабженный полосой прокрутки.

Классы ScrollDecorator и BorderDecorator являются подклассами Decorator - абстрактного класса, который представляет визуальные компоненты, применяемые для оформления других визуальных компонентов. VisualComponent - это абстрактный класс для представления визуальных объектов. В нем определен интерфейс для рисования и обработки событий. Отметим, что класс Decorator просто переадресует запросы на рисование своему компоненту, а его подклассы могут расширять эту операцию (см. рис. 2.13).

Структура

Рисунок 2.13 Структура паттерна Decorator

Результаты

У паттерна декоратор есть, по крайней мере, два плюса и два минуса:

  • большая гибкость, нежели у статического наследования. Паттерн декоратор позволяет более гибко добавлять объекту новые обязанности, чем было бы возможно в случае статического (множественного) наследования. Декоратор может добавлять и удалять обязанности во время выполнения программы. При использовании же наследования требуется создавать новый класс для каждой дополнительной обязанности (например, BorderedScrollableTextView, BorderedTextView), что ведет к увеличению числа классов и, как следствие, к возрастанию сложности системы. Кроме того, применение нескольких декораторов к одному компоненту позволяет произвольным образом сочетать обязанности. Декораторы позволяют легко добавить одно и то же свойство дважды. Например, чтобы окружить объект Textview двойной рамкой, нужно просто добавить два декоратора BorderDecorators. Двойное наследование классу Border в лучшем случае чревато ошибками;
  • позволяет избежать перегруженных функциями классов ни верхних уровнях иерархии. Декоратор разрешает добавлять новые обязанности по мере необходимости. Вместо того чтобы пытаться поддержать все мыслимые возможности в одном сложном, допускающем разностороннюю настройку классе, вы можете определить простой класс и постепенно наращивать его функциональность с помощью декораторов. В результате приложение уже не платит за неиспользуемые функции. Нетрудно также определять новые виды декораторов независимо от классов, которые они расширяют, даже если первоначально такие расширения не планировались. При расширении же сложного класса обычно приходится вникать в детали, не имеющие отношения к добавляемой функции;
  • декоратор и его компонент не идентичны. Декоратор действует как прозрачное обрамление. Но декорированный компонент все же не идентичен исходному. При использовании декораторов это следует иметь в виду;
  • множество мелких объектов. При использовании в проекте паттерна декоратор нередко получается система, составленная из большого числа мелких объектов, которые похожи друг на друга и различаются только способом взаимосвязи, а не классом и не значениями своих внутренних переменных. Хотя проектировщик, разбирающийся в устройстве такой системы, может легко настроить ее, но изучать и отлаживать ее очень тяжело.

Паттерн Facade

Название

Facade (фасад)

Назначение

Предоставляет унифицированный интерфейс вместо набора интерфейсов некоторой подсистемы. Фасад определяет интерфейс более высокого уровня, который упрощает использование подсистемы.

Разбиение на подсистемы облегчает проектирование сложной системы в целом. Общая цель всякого проектирования - свести к минимуму зависимость подсистем друг от друга и обмен информацией между ними. Один из способов решения этой задачи - введение объекта фасад, предоставляющий единый упрощенный интерфейс к более сложным системным средствам.

Структура

Рисунок 2.14 Структура паттерна Facade

Участники

Facade – фасад. Знает, каким классам подсистемы адресовать запрос; делегирует запросы клиентов подходящим объектам внутри подсистемы;

Классы подсистемы. Реализуют функциональность подсистемы; выполняют работу, порученную объектом Facade; ничего не "знают" о существовании фасада, то есть не хранят ссылок на него.

Отношения

Клиенты общаются с подсистемой, посылая запросы фасаду. Он переадресует их подходящим объектам внутри подсистемы. Хотя основную работу выполняют именно объекты подсистемы, фасаду, возможно, придется преобразовать свой Интерфейс в интерфейсы подсистемы. Клиенты, пользующиеся фасадом, не имеют прямого доступа к объектам подсистемы.

Результаты

У паттерна фасад есть следующие преимущества:

  • изолирует клиентов от компонентов подсистемы, уменьшая тем самым число объектов, с которыми клиентам приходится иметь дело, и упрощая работу с подсистемой;
  • позволяет ослабить связанность между подсистемой и ее клиентами. Зачастую компоненты подсистемы сильно связаны. Слабая связанность позволяет видоизменять компоненты, не затрагивая при этом клиентов. Фасады помогают разложить систему на слои и структурировать зависимости между объектами, а также избежать сложных и циклических зависимостей. Это может оказаться важным, если клиент и подсистема реализуются независимо Уменьшение числа зависимостей на стадии компиляции чрезвычайно важно в больших системах. Хочется, конечно, чтобы время, уходящее на перекомпиляцию после изменения классов подсистемы, было минимальным. Сокращение числа зависимостей за счет фасадов может уменьшить количество нуждающихся в повторной компиляции файлов после небольшой модификации какой-нибудь важной подсистемы. Фасад может также упростить процесс переноса системы на другие платформы, поскольку уменьшается вероятность того, что в результате изменения одной подсистемы понадобится изменять и все остальные;
  • фасад не препятствует приложениям напрямую обращаться к классам подсистемы, если это необходимо. Таким образом, у вас есть выбор между простотой и общностью.

Паттерн Low Coupling

Название

Low Coupling (Низкая связность)

Проблема

Степень связанности (coupling) — это мера, определяющая насколько жестко один элемент связан с другими элементами, либо каким количеством данных о других элементах он обладает. Элемент с низкой степенью связанности (или слабым связыванием) зависит от не очень большого числа других элементов. Выражение "очень много" зависит от контекста, однако необходимо провести его оценку.

Класс с высокой степенью связанности (или жестко связанный) зависит от множества других классов. Однако наличие таких классов нежелательно, поскольку оно приводит к возникновению следующих проблем.

  • Изменения в связанных классах приводят к локальным изменениям в данном классе.
  • Затрудняется понимание каждого класса в отдельности.
  • Усложняется повторное использование, поскольку для этого требуется дополнительный анализ классов, с которыми связан данный класс.

Решение

Распределить обязанности таким образом, чтобы степень связанности оставалась низкой.

Результаты

  • Изменения компонентов мало сказываются на других объектах.
  • Принципы работы и функции компонентов можно понять, не изучая другие объекты.
  • Удобство повторного использования.

Пример

Пусть в системе имеются классы Payment, Sale и Register. Предположим, что необходимо создать экземпляр класса Payment и связать его с объектом Sale. Какой класс должен отвечать за выполнение этой операции? Поскольку в реальной предметной области регистрация объекта Payment выполняется объектом Register, в соответствии с шаблоном Creator, объект Register является хорошим кандидатом для создания объекта Payment. Затем экземпляр объекта Register должен передать сообщение addPayment объекту Sale, указав в качестве параметра новый объект Payment. Приведенные рассуждения отражены на фрагменте диаграммы взаимодействий, представленной на рис. 2.15.Такое распределение обязанностей предполагает, ЧТО классRegister обладает знаниями оданных класса Payment (т.е. связывается с ним).

Обратите внимание на обозначения, принятые в языке UML. Экземпляру объекта Payment присвоено явное имя р, чтобы его можно было использовать в качестве параметра сообщения 2.

Рисунок 2.15 Диаграмма взаимодействия. Сильная связность

Альтернативный способ создания объекта Payment и его связывания с объектом Sale состоит в том, что бы возложит обязанность за создание объекта Payment на Sale (рис. 2.16)

Рисунок 2.16 Диаграмма взаимодействия. Слабая связность









Не нашли то, что искали? Воспользуйтесь поиском гугл на сайте:


©2015- 2018 zdamsam.ru Размещенные материалы защищены законодательством РФ.