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

  • 12.2.4 Декоратор (Decorator)

  • 12.2.5 Прокси (Proxy)

  • 12.3 Поведенческие паттерны проектирования

  • 12.3.1 Команда (Command)

  • 12.3.2 Итератор (Iterator)

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


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

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

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

    необходимо объединять группы схожих объектов и управлять ими;

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

    код клиента работает с примитивными и составными объектами еди- нообразно.
    Плюсы:

    в систему легко добавлять новые примитивные или составные объ- екты, так как паттерн Composite использует общий базовый класс
    Component;

    код клиента имеет простую структуру – примитивные и составные объекты обрабатываются одинаковым образом;

    127

    паттерн Composite позволяет легко обойти все узлы древовидной структуры.
    Минусы:
    Неудобно осуществить запрет на добавление в составной объект
    Composite объектов определенных типов. Так, например, в состав римской армии не могут входить боевые слоны.
    Применение:
    Управление группами объектов может быть непростой задачей, осо- бенно, если эти объекты содержат собственные объекты.
    Для военной стратегической игры «Пунические войны», описывающей военное противостояние между Римом и Карфагеном, каждая боевая еди- ница (всадник, лучник, пехотинец) имеет свою собственную разрушающую силу. Эти единицы могут объединяться в группы для образования более сложных военных подразделений, например, римские легионы, которые, в свою очередь, объединяясь, образуют целую армию. Как рассчитать боевую мощь таких иерархических соединений?
    Паттерн Composite предлагает следующее решение. Он вводит абстракт- ный базовый класс
    Component с поведением, общим для всех примитивных и составных объектов. Для случая стратегической игры – это метод getStrength()
    для подсчета разрушающей силы. Подклассы
    Primitive и
    Composite являются производными от класса
    Component
    . Составной объект
    Composite хранит компоненты-потомки абстрактного типа
    Component
    , каж- дый из которых может быть также
    Composite
    #include
    #include
    #include
    // Component class Unit
    { public: virtual int getStrength() = 0; virtual void addUnit(Unit* p) { assert( false);
    } virtual

    Unit() {}
    };
    // Primitives class Archer: public Unit
    { public: virtual int getStrength() { return 1;
    }

    128
    }; class Infantryman: public Unit
    { public: virtual int getStrength() { return 2;
    }
    }; class Horseman: public Unit
    { public: virtual int getStrength() { return 3;
    }
    };
    // Composite class CompositeUnit: public Unit
    { public: int getStrength() { int total = 0; for(int i=0; igetStrength(); return total;
    } void addUnit(Unit* p) { c.push_back( p);
    }
    CompositeUnit() { for(int i=0; i } private: std::vector c;
    };
    // Вспомогательная функция для создания легиона
    CompositeUnit* createLegion()
    {
    // Римский легион содержит:
    CompositeUnit* legion = new CompositeUnit;
    // 3000 тяжелых пехотинцев for (int i=0; i<3000; ++i) legion->addUnit(new Infantryman);
    // 1200 легких пехотинцев for (int i=0; i<1200; ++i) legion->addUnit(new Archer);
    // 300 всадников for (int i=0; i<300; ++i) legion->addUnit(new Horseman); return legion;
    } int main()
    {

    129
    // Римская армия состоит из 4-х легионов
    CompositeUnit* army = new CompositeUnit; for (int i=0; i<4; ++i) army->addUnit( createLegion()); cout << "Roman army damaging strength is "
    << army->getStrength() << endl;
    // … delete army; return 0;
    }
    Следует обратить внимание на один важный момент. Абстрактный базо- вый класс
    Unit объявляет интерфейс для добавления новых боевых единиц addUnit()
    , несмотря на то, что объектам примитивных типов (
    Archer
    ,
    Infantryman
    ,
    Horseman
    ) подобная операция не нужна. Сделано это в угоду прозрачности системы в ущерб ее безопасности. Клиент знает, что объект типа
    Unit всегда будет иметь метод addUnit()
    . Однако его вызов для при- митивных объектов считается ошибочным и небезопасным.
    12.2.4 Декоратор (Decorator)
    Декоратор (Decorator)

    структурный шаблон проектирования, предна- значенный для динамического подключения дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу практике созданию подклассов с целью расширения функциональности.
    Назначение:

    Паттерн Decorator динамически добавляет новые обязанности объ- екту. Декораторы являются гибкой альтернативой порождению подклассов для расширения функциональности.

    Рекурсивно декорирует основной объект.

    Паттерн Decorator использует схему «обертываем подарок, кладем его в коробку, обертываем коробку».
    Плюсы

    нет необходимости создавать подклассы для расширения функцио- нальности объекта;

    возможность динамически подключать новую функциональность до или после основной функциональности объекта
    ConcreteComponent
    Применение:
    Клиент всегда заинтересован в функциональности
    CoreFunctionality.doThis()
    . Клиент может или не может быть заинтере- сован в методах
    OptionalOne.doThis()
    и
    OptionalTwo.doThis()
    . Каждый

    130 из этих классов переадресует запрос базовому классу
    Decorator
    , а тот направляет его в декорируемый объект.
    Паттерн Decorator динамически добавляет новые обязанности объекту.
    Украшения для новогодней елки являются примерами декораторов. Огни, гирлянды, игрушки и т.д. вешают на елку для придания ей праздничного вида. Украшения не меняют саму елку, а только делают ее новогодней.
    Хотя картины можно повесить на стену и без рамок, рамки часто добав- ляются для придания нового стиля. class I { public: virtual I(){} virtual void do_it() = 0;
    }; class A: public I { public:
    A() { cout << "A dtor" << '\n';
    }
    /*virtual*/ void do_it() { cout << 'A';
    }
    }; class D: public I { public:
    D(I *inner) { m_wrappee = inner;
    }
    D() { delete m_wrappee;
    }
    /*virtual*/ void do_it() { m_wrappee->do_it();
    } private:
    I *m_wrappee;
    }; class X: public D { public:
    X(I *core): D(core){}
    X() { cout << "X dtor" << " ";
    }
    /*virtual*/ void do_it() {
    D::do_it(); cout << 'X';
    }
    }; class Y: public D {

    131 public:
    Y(I *core): D(core){}
    Y() { cout << "Y dtor" << " ";
    }
    /*virtual*/ void do_it() {
    D::do_it(); cout << 'Y';
    }
    }; class Z: public D { public:
    Z(I *core): D(core){}
    Z() { cout << "Z dtor" << " ";
    }
    /*virtual*/ void do_it() {
    D::do_it(); cout << 'Z';
    }
    }; int main() {
    I *anX = new X(new A);
    I *anXY = new Y(new X(new A));
    I *anXYZ = new Z(new Y(new X(new A))); anX->do_it(); cout << '\n'; anXY->do_it(); cout << '\n'; anXYZ->do_it(); cout << '\n'; delete anX; delete anXY; delete anXYZ;
    }
    Вывод программы:
    AX
    AXY
    AXYZ
    X dtor A dtor
    Y dtor X dtor A dtor
    Z dtor Y dtor X dtor A dtor
    12.2.5 Прокси (Proxy)
    Прокси (Proxy)

    структурный шаблон проектирования, предназначен- ный для замещения другого объекта для контроля доступа к нему.
    Назначение:

    Паттерн Proxy является суррогатом или замеcтителем другого объекта и контролирует доступ к нему.

    132

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

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

    Виртуальный proxy является заместителем объектов, создание кото- рых обходится дорого. Реальный объект создается только при первом за- просе/доступе клиента к объекту.

    Удаленный proxy предоставляет локального представителя для объ- екта, который находится в другом адресном пространстве («заглушки» в
    RPC и CORBA).

    Защитный proxy контролирует доступ к основному объекту. «Сурро- гатный» объект предоставляет доступ к реальному объекту, только вызыва- ющий объект имеет соответствующие права.

    Интеллектуальный proxy выполняет дополнительные действия при доступе к объекту.

    Вот типичные области применения интеллектуальных proxy:

    Подсчет числа ссылок на реальный объект. При отсутствии ссылок па- мять под объект автоматически освобождается (известен также как интел- лектуальный указатель или smart pointer).

    Загрузка объекта в память при первом обращении к нему.

    Установка запрета на изменение реального объекта при обращении к нему других объектов.
    Паттерн Proxy для доступа к реальному объекту использует его суррогат или заместитель. Банковский чек является заместителем денежных средств на счете. Чек может быть использован вместо наличных денег для соверше- ния покупок и, в конечном счете, контролирует доступ к наличным деньгам на счете чекодателя.
    Особенности паттерна Proxy:

    133

    Adapter предоставляет своему объекту другой интерфейс. Proxy предоставляет тот же интерфейс. Decorator предоставляет расширенный интерфейс.

    Decorator и Proxy имеют разные цели, но схожие структуры. Оба вво- дят дополнительный уровень косвенности: их реализации хранят ссылку на объект, на который они отправляют запросы. class RealImage
    { int m_id; public:
    RealImage(int i)
    { m_id = i; cout << " $$ ctor: " << m_id << '\n';
    }
    RealImage()
    { cout << " dtor: " << m_id << '\n';
    } void draw()
    { cout << " drawing image " << m_id << '\n';
    }
    };
    // 1. Класс-обертка с "дополнительным уровнем косвенности" class Image
    {
    // 2. Класс-обертка содержит указатель на реальный класс
    RealImage *m_the_real_thing; int m_id; static int s_next; public:
    Image()
    { m_id = s_next++;
    // 3. Инициализируется нулевым значением m_the_real_thing = 0;
    }
    Image()
    { delete m_the_real_thing;
    } void draw()
    {
    // 4. Реальный объект создается при поступлении
    // запроса "на первом использовании" if (!m_the_real_thing) m_the_real_thing = new RealImage(m_id);
    // 5. Запрос всегда делегируется реальному объекту m_the_real_thing->draw();
    }
    }; int Image::s_next = 1; int main()

    134
    {
    Image images[5]; for (int i; true;)
    { cout << "Exit[0], Image[1-5]: "; cin >> i; if (i == 0) break; images[i - 1].draw();
    }
    }
    12.3 Поведенческие паттерны проектирования
    Паттерны поведения рассматривают вопросы о связях между объектами и распределением обязанностей между ними. Для этого могут использо- ваться механизмы, основанные как на наследовании, так и на композиции.
    К поведенческим паттернам проектирования относятся следующие:

    команда (command);

    итератор (iterator);

    наблюдатель (observer);

    стратегия (strategy);

    шаблонный метод (template method);

    посетитель (visitor).
    12.3.1 Команда (Command)
    Команда (Command)

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

    Система управляется событиями. При появлении такого события (за- проса) необходимо выполнить определенную последовательность дей- ствий.

    Необходимо параметризировать объекты выполняемым действием, ставить запросы в очередь или поддерживать операции отмены (undo) и по- втора (redo) действий.

    Нужен объектно-ориентированный аналог функции обратного вызова в процедурном программировании.
    Плюсы:

    135
    Придает системе гибкость, отделяя инициатора запроса от его получа- теля, позволяет осуществлять динамическую замену команд, использовать сложные составные команды, осуществлять отмену операций.
    Применение:
    Паттерн Command преобразовывает запрос на выполнение действия в отдельный объект-команду. Такая инкапсуляция позволяет передавать эти действия другим функциям и объектам в качестве параметра, приказывая им выполнить запрошенную операцию. Команда – это объект, поэтому над ней допустимы любые операции, что и над объектом.
    Интерфейс командного объекта определяется абстрактным базовым классом
    Command и в самом простом случае имеет единственный метод execute()
    . Производные классы определяют получателя запроса (указатель на объект-получатель) и необходимую для выполнения операцию (метод этого объекта). Метод execute()
    подклассов
    Command просто вызывает нужную операцию получателя.
    В паттерне Command может быть до трех участников:

    клиент, создающий экземпляр командного объекта;

    инициатор запроса, использующий командный объект;

    получатель запроса.
    Сначала клиент создает объект
    ConcreteCommand
    , конфигурируя его по- лучателем запроса. Этот объект также доступен инициатору. Инициатор ис- пользует его при отправке запроса, вызывая метод execute()
    . Этот алго- ритм напоминает работу функции обратного вызова в процедурном про- граммировании – функция регистрируется, чтобы быть вызванной позднее.
    Паттерн Command отделяет объект, инициирующий операцию, от объ- екта, который знает, как ее выполнить. Единственное, что должен знать ини- циатор, это как отправить команду. Это придает системе гибкость: позво- ляет осуществлять динамическую замену команд, использовать сложные со- ставные команды, осуществлять отмену операций.
    #include
    #include
    #include class Game { public: void create( ) { cout << "Create game " << endl;
    } void open( string file ) { cout << "Open game from " << file << endl;
    } void save( string file ) {

    136 cout << "Save game in " << file << endl;
    } void make_move( string move ) { cout << "Make move " << move << endl;
    }
    }; string getPlayerInput( string prompt ) { string input; cout << prompt; cin >> input; return input;
    }
    // Базовый класс class Command { public: virtual Command() {} virtual void execute() = 0; protected:
    Command( Game* p ): pgame( p) {}
    Game * pgame;
    }; class CreateGameCommand: public Command { public:
    CreateGameCommand( Game * p ) : Command( p) {} void execute() { pgame->create( );
    }
    }; class OpenGameCommand: public Command { public:
    OpenGameCommand( Game * p ) : Command( p) {} void execute() { string file_name; file_name = getPlayerInput( "Enter file name:"); pgame->open( file_name);
    }
    }; class SaveGameCommand: public Command { public:
    SaveGameCommand( Game * p ) : Command( p) {} void execute( ) { string file_name; file_name = getPlayerInput( "Enter file name:"); pgame->save( file_name);
    }
    }; class MakeMoveCommand: public Command { public:
    MakeMoveCommand( Game * p) : Command( p) {} void execute() {
    // Сохраним игру для возможного последующего отката pgame->save("TEMP_FILE"); string move; move = getPlayerInput( "Enter your move:"); pgame->make_move(move);

    137
    }
    }; class UndoCommand: public Command { public:
    UndoCommand( Game * p ) : Command( p) {} void execute() {
    // Восстановим игру из временного файла pgame->open( "TEMP_FILE");
    }
    }; int main()
    {
    Game game;
    // Имитация действий игрока vector v;
    // Создаем новую игру v.push_back( new CreateGameCommand( &game));
    // Делаем несколько ходов v.push_back( new MakeMoveCommand( &game)); v.push_back( new MakeMoveCommand( &game));
    // Последний ход отменяем v.push_back( new UndoCommand( &game));
    // Сохраняем игру v.push_back( new SaveGameCommand( &game)); for (size_t i=0; iexecute(); for (size_t i=0; i}
    12.3.2 Итератор (Iterator)
    Итератор (Iterator) – Шаблон проектирования, предоставляющий меха- низм обхода элементов составных объектов (коллекций) не раскрывая их внутреннего представления.
    Назначение:

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

    Абстракция в стандартных библиотеках C++ и Java, позволяющая раз- делить классы коллекций и алгоритмов.

    Придает обходу коллекции «объектно-ориентированный статус».

    Полиморфный обход.
    Применение:

    138
    Составной объект, такой как список, должен предоставлять способ до- ступа к его элементам без раскрытия своей внутренней структуры. Более того, иногда нужно перебирать элементы списка различными способами, в зависимости от конкретной задачи. Но вы, вероятно, не хотите раздувать интерфейс списка операциями для различных обходов, даже если они необ- ходимы. Кроме того, иногда нужно иметь несколько активных обходов од- ного списка одновременно. Было бы хорошо иметь единый интерфейс для обхода разных типов составных объектов (т.е. полиморфная итерация).
    Паттерн Iterator позволяет все это делать. Ключевая идея состоит в том, чтобы ответственность за доступ и обход переместить из составного объекта на объект Iterator, который будет определять стандартный протокол обхода.
    Абстракция Iterator имеет основополагающее значение для технологии, называемой «обобщенное программирование». Эта технология четко разде- ляет такие понятия как «алгоритм» и «структура данных». Мотивирующие факторы: способствование компонентной разработке, повышение произво- дительности и снижение расходов на управление.
    Рассмотрим пример. Если вы хотите одновременно поддерживать че- тыре вида структур данных (массив, бинарное дерево, связанный список и хэш-таблица) и три алгоритма (сортировка, поиск и слияние), то традицион- ный подход потребует 12 вариантов конфигураций (четыре раза по три), в то время как обобщенное программирование требует лишь 7 (четыре плюс три).
    #include using namespace std; class Stack { int items[10]; int sp; public: friend class StackIter;
    Stack()
    { sp = -1;
    } void push(int in)
    { items[++sp] = in;
    } int pop()
    { return items[sp--];
    } bool isEmpty()
    { return (sp == -1);
    }
    };

    139 class StackIter { const Stack &stk; int index; public:
    StackIter(const Stack &s): stk(s)
    { index = 0;
    } void operator++()
    { index++;
    } bool operator()()
    { return index != stk.sp + 1;
    } int operator *()
    { return stk.items[index];
    }
    }; bool operator == (const Stack &l, const Stack &r)
    {
    StackIter itl(l), itr(r); for (; itl(); ++itl, ++itr) if (*itl != *itr) break; return !itl() && !itr();
    } int main()
    {
    Stack s1; int i; for (i = 1; i < 5; i++) s1.push(i);
    Stack s2(s1), s3(s1), s4(s1), s5(s1); s3.pop(); s5.pop(); s4.push(2); s5.push(9); cout << "1 == 2 is " << (s1 == s2) << endl; cout << "1 == 3 is " << (s1 == s3) << endl; cout << "1 == 4 is " << (s1 == s4) << endl; cout << "1 == 5 is " << (s1 == s5) << endl;
    }
    1   ...   7   8   9   10   11   12   13   14   15


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