Главная страница
Навигация по странице:

  • 12.3.4 Стратегия (Strategy)

  • 12.3.5 Шаблонный метод (Template method)

  • 12.3.6 Посетитель (Visitor)

  • 13.1 Основные компоненты STL

  • ттттт. Объектноориентированное программирование


    Скачать 1.73 Mb.
    НазваниеОбъектноориентированное программирование
    Анкорттттт
    Дата30.10.2021
    Размер1.73 Mb.
    Формат файлаpdf
    Имя файлаOOP-PrePrint.pdf
    ТипКонспект
    #259341
    страница13 из 15
    1   ...   7   8   9   10   11   12   13   14   15

    12.3.3 Наблюдатель (Observer)
    Наблюдатель (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 class Compression
    { 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 using namespace std; class Base
    { 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 но при этом необходимо помнить, что главной особенностью ключа является возможность применить к нему операцию сравнения. Быстрый поиск значения по ключу осуществляется благодаря тому, что пары хранятся в отсортированном виде. Эта коллекция имеет со- ответственно и недостаток – скорость вставки новой пары обратно пропор- циональна количеству элементов, сохраненных в коллекции, поскольку про- сто добавить новое значение в конец коллекции не получится. Еще одна

    151 важная вещь, которую необходимо помнить при использовании данной кол- лекции – ключ должен быть уникальным. Для того, чтобы начать использо- вание данной коллекции, включите
    #include
    . Если вы хотите ис- пользовать данную коллекцию, чтобы избежать дубликатов, то вы избежите их только по ключу. set
    – это коллекция уникальных значений const Key
    , каждое из кото- рых является также и ключом – то есть, проще говоря, это отсортированная коллекция, предназначенная для быстрого поиска необходимого значения.
    К ключу предъявляются те же требования, что и в случае ключа для map.
    Естественно, использовать ее для этой цели нет смысла, если вы хотите со- хранить в ней простые типы данных, по меньшей мере вам необходимо определить свой класс, хранящий пару ключ – значение и определяющий операцию сравнения по ключу. Очень удобно использовать данную коллек- цию, если вы хотите избежать повторного сохранения одного и того же зна- чения. Для того, чтобы начать использование данной коллекции, включите
    #include
    multimap
    – это модифицированный map
    , в котором отсутствует требова- ния уникальности ключа – то есть, если вы произведете поиск по ключу, то вам вернется не одно значение, а набор значений, сохраненных с данным ключом. Для того, чтобы начать использование данной коллекции включите
    #include
    multiset
    – то же самое относится и к этой коллекции, требования уни- кальности ключа в ней не существует, что приводит к возможности хране- ния дубликатов значений. Тем не менее, существует возможность быстрого нахождения значений по ключу в случае, если вы определили свой класс.
    Поскольку все значения в map и set хранятся в отсортированном виде, то получается, что в этих коллекциях мы можем очень быстро отыскать необ- ходимое нам значение по ключу, но при этом операция вставки нового эле- мента
    T
    будет стоить нам несколько дороже, чем например в vector
    . Для того, чтобы начать использование данной коллекции, включите
    #include

    1   ...   7   8   9   10   11   12   13   14   15


    написать администратору сайта