ттттт. Объектноориентированное программирование
Скачать 1.73 Mb.
|
Наблюдатель (Observer) – поведенческий шаблон проектирования. Также известен как «подчинённые» (Dependents), «издатель-подписчик» (Publisher-Subscriber). Назначение: 140 Определяет зависимость типа «один ко многим» между объектами та- ким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом событии. Инкапсулирует главный (независимый) компонент в абстракцию Subject и изменяемые (зависимые) компоненты в иерархию Observer. Определяет часть View в модели Model-View-Controller (MVC). Применение: Имеется система, состоящая из множества взаимодействующих классов. При этом взаимодействующие объекты должны находиться в согласован- ных состояниях. Вы хотите избежать монолитности такой системы, сделав классы слабо связанными (или повторно используемыми). При реализации шаблона «наблюдатель» обычно используются следую- щие классы: Observable – интерфейс, определяющий методы для добавления, уда- ления и оповещения наблюдателей. Observer – интерфейс, с помощью которого наблюдаемый объект опо- вещает наблюдателей. ConcreteObservable – конкретный класс, который реализует интерфейс Observable. ConcreteObserver – конкретный класс, который реализует интерфейс Observer. Шаблон «наблюдатель» применяется в тех случаях, когда система обла- дает следующими свойствами: существует, как минимум, один объект, рассылающий сообщения; имеется не менее одного получателя сообщений, причём их количе- ство и состав могут изменяться во время работы приложения. Данный шаблон часто применяют в ситуациях, в которых отправителя сообщений не интересует, что делают с предоставленной им информацией получатели. class Observer { public: virtual void update(int value) = 0; }; class Subject { int m_value; vector m_views; public: void attach(Observer *obs) { m_views.push_back(obs); 141 } void set_val(int value) { m_value = value; notify(); } void notify() { for (int i = 0; i < m_views.size(); ++i) m_views[i]->update(m_value); } }; class DivObserver: public Observer { int m_div; public: DivObserver(Subject *model, int div) { model->attach(this); m_div = div; } /* virtual */void update(int v) { cout << v << " div " << m_div << " is " << v / m_div << '\n'; } }; class ModObserver: public Observer { int m_mod; public: ModObserver(Subject *model, int mod) { model->attach(this); m_mod = mod; } /* virtual */void update(int v) { cout << v << " mod " << m_mod << " is " << v % m_mod << '\n'; } }; int main() { Subject subj; DivObserver divObs1(&subj, 4); DivObserver divObs2(&subj, 3); ModObserver modObs3(&subj, 3); subj.set_val(14); } Вывод программы: 14 div 4 is 3 14 div 3 is 4 14 mod 3 is 2 142 12.3.4 Стратегия (Strategy) Стратегия (Strategy) – поведенческий шаблон проектирования. Перено- сит семейство алгоритмов в отдельную иерархию классов, что позволяет за- менять один алгоритм другим в ходе выполнения программы. Назначение: Существуют системы, поведение которых может определяться согласно одному алгоритму из некоторого семейства. Все алгоритмы этого семейства являются родственными: предназначены для решения общих задач, имеют одинаковый интерфейс для использования и отличаются только реализа- цией (поведением). Пользователь, предварительно настроив программу на нужный алгоритм (выбрав стратегию), получает ожидаемый результат. Как пример, – приложение, предназначенное для компрессии файлов использует один из доступных алгоритмов: zip, arj или rar. Объектно-ориентированный дизайн такой программы может быть по- строен на идее использования полиморфизма. В результате получаем набор родственных классов с общим интерфейсом и различными реализациями алгоритмов. Представленному подходу свойственны следующие недостатки: Реализация алгоритма жестко привязана к его подклассу, что затруд- няет поддержку и расширение такой системы. Система, построенная на основе наследования, является статичной. Заменить один алгоритм на другой в ходе выполнения программы уже не- возможно. Применение паттерна Strategy позволяет устранить указанные недо- статки. Применение: Паттерн Strategy переносит в отдельную иерархию классов все детали, связанные с реализацией алгоритмов. Для случая программы сжатия файлов абстрактный базовый класс Compression этой иерархии объявляет интер- фейс, общий для всех алгоритмов и используемый классом Compressor Подклассы ZIP_Compression , ARJ_Compression и RAR_Compression его реализуют в соответствии с тем или иным алгоритмом. Класс Compressor содержит указатель на объект абстрактного типа Compression и предназна- чен для переадресации пользовательских запросов конкретному алгоритму. Для замены одного алгоритма другим достаточно перенастроить этот указа- тель на объект нужного типа. #include 143 #include { public: virtual Compression() {} virtual void compress( const string & file ) = 0; }; class ZIP_Compression : public Compression { public: void compress( const string & file ) { cout << "ZIP compression" << endl; } }; class ARJ_Compression : public Compression { public: void compress( const string & file ) { cout << "ARJ compression" << endl; } }; class RAR_Compression : public Compression { public: void compress( const string & file ) { cout << "RAR compression" << endl; } }; class Compressor { public: Compressor( Compression* comp): p(comp) {} Compressor() { delete p; } void compress( const string & file ) { p->compress( file); } private: Compression* p; }; int main() { Compressor* p = new Compressor( new ZIP_Compression); p->compress( "file.txt"); delete p; return 0; } Плюсы: Систему проще поддерживать и модифицировать, так как семейство алгоритмов перенесено в отдельную иерархию классов. 144 Паттерн Strategy предоставляет возможность замены одного алго- ритма другим в процессе выполнения программы. Паттерн Strategy позволяет скрыть детали реализации алгоритмов от клиента. Минусы: Для правильной настройки системы пользователь должен знать об особенностях всех алгоритмов. Число классов в системе, построенной с применением паттерна Strategy, возрастает. 12.3.5 Шаблонный метод (Template method) Шаблонный метод (Template Method) – шаблон проектирования, опреде- ляющий основу алгоритма и позволяющий подклассам изменить некоторые шаги этого алгоритма без изменения его общей структуры. Назначение: Имеются два разных, но в тоже время очень похожих компонента. Вы хотите внести изменения в оба компонента, избежав дублирования кода. Ба- зовый класс определяет шаги алгоритма с помощью абстрактных операций, а производные классы их реализуют. Применение: Проектировщик компонента решает, какие шаги алгоритма являются неизменными (или стандартными), а какие изменяемыми (или настраивае- мыми). Абстрактный базовый класс реализует стандартные шаги алгоритма и может предоставлять (или нет) реализацию по умолчанию для настраива- емых шагов. Изменяемые шаги могут (или должны) предоставляться клиен- том компонента в конкретных производных классах. Проектировщик компонента определяет необходимые шаги алгоритма, порядок их выполнения, но позволяет клиентам компонента расширять или замещать некоторые из этих шагов. Паттерн Template Method широко применяется в каркасах приложений (frameworks). Каждый каркас реализует неизменные части архитектуры в предметной области, а также определяет те части, которые могут или должны настраиваться клиентом. Таким образом, каркас приложения стано- вится «центром вселенной», а настройки клиента являются просто «третьей планетой от Солнца». Эту инвертированную структуру кода ласково назы- вают принципом Голливуда – «Не звоните нам, мы сами вам позвоним». 145 Паттерн Template Method определяет основу алгоритма и позволяет под- классам изменить некоторые шаги этого алгоритма без изменения его общей структуры. Строители зданий используют шаблонный метод при проекти- ровании новых домов. Здесь могут использоваться уже существующие ти- повые планы, в которых модифицируются только отдельные части. #include { void a() { cout << "a "; } void c() { cout << "c "; } void e() { cout << "e "; } // 2. Для шагов, требующих особенной реализации, определите // "замещающие" методы. virtual void ph1() = 0; virtual void ph2() = 0; public: // 1. Стандартизуйте основу алгоритма в шаблонном методе // базового класса void execute() { a(); ph1(); c(); ph2(); e(); } }; class One: public Base { // 3. Производные классы реализуют "замещающие" методы. /*virtual*/void ph1() { cout << "b "; } /*virtual*/void ph2() { cout << "d "; } }; class Two: public Base { /*virtual*/void ph1() { cout << "2 "; 146 } /*virtual*/void ph2() { cout << "4 "; } }; int main() { Base *array[] = { &One(), &Two() }; for (int i = 0; i < 2; i++) { array[i]->execute(); cout << '\n'; } } Вывод программы: a b c d e a 2 c 4 e 12.3.6 Посетитель (Visitor) Посетитель (Visitor) – шаблон проектирования, определяющий опера- цию, выполняемую на каждом элементе из некоторой структуры без изме- нения классов этих объектов. Назначение: Является классической техникой для восстановления потерянной ин- формации о типе. Паттерн Visitor позволяет выполнить нужные действия в зависимости от типов двух объектов. Предоставляет механизм двойной диспетчеризации. Применение: Различные и несвязанные операции должны выполняться над узловыми объектами некоторой гетерогенной совокупной структуры. Вы хотите избе- жать «загрязнения» классов этих узлов такими операциями (то есть избе- жать добавления соответствующих методов в эти классы). И вы не хотите запрашивать тип каждого узла и осуществлять приведение указателя к пра- вильному типу, прежде чем выполнить нужную операцию. Основным назначением паттерна Visitor является введение абстрактной функциональности для совокупной иерархической структуры объектов «элемент», а именно, паттерн Visitor позволяет, не изменяя классы Element , 147 добавлять в них новые операции. Для этого вся обрабатывающая функцио- нальность переносится из самих классов Element (эти классы становятся «легковесными») в иерархию наследования Visitor При этом паттерн Visitor использует технику «двойной диспетчериза- ции». Обычно при передаче запросов используется «одинарная диспетчери- зация» – то, какая операция будет выполнена для обработки запроса, зави- сит от имени запроса и типа получателя. В «двойной диспетчеризации» вы- зываемая операция зависит от имени запроса и типов двух получателей (типа Visitor и типа посещаемого элемента Element ). Реализуйте паттерн Visitor следующим образом. Создайте иерархию классов Visitor , в абстрактном базовом классе которой для каждого под- класса Element совокупной структуры определяется чисто виртуальный ме- тод visit() . Каждый метод visit() принимает один аргумент – указатель или ссылку на подкласс Element Каждая новая добавляемая операция моделируется при помощи конкрет- ного подкласса Visitor . Подклассы Visitor реализуют visit() методы, объявленные в базовом классе Visitor Добавьте один чисто виртуальный метод accept() в базовый класс иерархии Element . В качестве параметра accept() принимает единствен- ный аргумент – указатель или ссылку на абстрактный базовый класс иерар- хии Visitor Каждый конкретный подкласс Element реализует метод accept() сле- дующим образом: используя полученный в качестве параметра адрес экзем- пляра подкласса Visitor , просто вызывает его метод visit() , передавая в качестве единственного параметра указатель this Теперь «элементы» и «посетители» готовы. Если клиенту нужно выпол- нить какую-либо операцию, то он создает экземпляр объекта соответствую- щего подкласса Visitor и вызывает accept() метод для каждого объекта Element , передавая экземпляр Visitor в качестве параметра. При вызове метода accept() ищется правильный подкласс Element . За- тем, при вызове метода visit() программное управление передается пра- вильному подклассу Visitor . Таким образом, двойная диспетчеризация по- лучается как сумма одинарных диспетчеризаций сначала в методе accept() , а затем в методе visit() Паттерн Visitor позволяет легко добавлять новые операции – нужно про- сто добавить новый производный от Visitor класс. Однако паттерн Visitor следует использовать только в том случае, если подклассы Element сово- купной иерархической структуры остаются стабильными (неизменяемыми). 148 В противном случае, нужно приложить значительные усилия на обновление всей иерархии Visitor class Color { public: virtual void accept(class Visitor*) = 0; }; class Red: public Color { public: /*virtual*/void accept(Visitor*); void eye() { cout << "Red::eye\n"; } }; class Blu: public Color { public: /*virtual*/void accept(Visitor*); void sky() { cout << "Blu::sky\n"; } }; class Visitor { public: virtual void visit(Red*) = 0; virtual void visit(Blu*) = 0; }; class CountVisitor: public Visitor { public: CountVisitor() { m_num_red = m_num_blu = 0; } /*virtual*/void visit(Red*) { ++m_num_red; } /*virtual*/void visit(Blu*) { ++m_num_blu; } void report_num() { cout << "Reds " << m_num_red << ", Blus " << m_num_blu << '\n'; } private: int m_num_red, m_num_blu; }; class CallVisitor: public Visitor { 149 public: /*virtual*/void visit(Red *r) { r->eye(); } /*virtual*/void visit(Blu *b) { b->sky(); } }; void Red::accept(Visitor *v) { v->visit(this); } void Blu::accept(Visitor *v) { v->visit(this); } int main() { Color *set[] = { new Red, new Blu, new Blu, new Red, new Red, 0 }; CountVisitor count_operation; CallVisitor call_operation; for (int i = 0; set[i]; i++) { set[i]->accept(&count_operation); set[i]->accept(&call_operation); } count_operation.report_num(); } Вывод программы: Red::eye Blu::sky Blu::sky Red::eye Red::eye Reds 3, Blus 2 150 13. ИСПОЛЬЗОВАНИЕ STL В C++ Механизм шаблонов был встроен в компилятор C++ с целью дать воз- можность программистам C++ создавать эффективные и компактные биб- лиотеки. Через некоторое время была создана одна из библиотек, которая впоследствии и стала стандартной частью C++. STL это самая эффективная библиотека для C++, существующая на сегодняшний день. Сегодня существует множество реализаций стандартной библиотеки шаблонов, которые следуют стандарту, но при этом предлагают свои рас- ширения, что является с одной стороны плюсом, но, с другой, не очень хо- рошо, поскольку не всегда можно использовать код повторно с другим ком- пилятором. Поэтому предпочтительно оставаться в рамках стандарта, даже если вы в дальнейшем очень хорошо разберетесь с реализацией вашей биб- лиотеки. 13.1 Основные компоненты STL Каждая STL коллекция имеет собственный набор шаблонных парамет- ров, который необходим ей для того, чтобы на базе шаблона реализовать тот или иной класс, максимально приспособленный для решения конкретных задач. Какой тип коллекции использовать, зависит от задач, поэтому необ- ходимо знать их внутреннее устройство для наиболее эффективного исполь- зования. Рассмотрим наиболее часто используемые типы коллекций. vector – коллекция элементов Т , сохраненных в массиве, увеличивае- мом по мере необходимости. Для того, чтобы начать использование данной коллекции, включите #include list – коллекция элементов Т , сохраненных, как двунаправленный свя- занный список. Для того, чтобы начать использование данной коллекции, включите #include map – это коллекция, сохраняющая пары значений pair Эта коллекция предназначена для быстрого поиска значения T по ключу const Key . В качестве ключа может быть использовано все, что угодно, например, строка или int но при этом необходимо помнить, что главной особенностью ключа является возможность применить к нему операцию сравнения. Быстрый поиск значения по ключу осуществляется благодаря тому, что пары хранятся в отсортированном виде. Эта коллекция имеет со- ответственно и недостаток – скорость вставки новой пары обратно пропор- циональна количеству элементов, сохраненных в коллекции, поскольку про- сто добавить новое значение в конец коллекции не получится. Еще одна |