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

  • 12.1.5 Одиночка (Singleton)

  • 12.2 Структурные паттерны проектирования

  • 12.2.1 Адаптер (Adapter)

  • 12.2.2 Мост (Bridge)

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


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

    12.1.4 Прототип (Prototype)
    Прототип (Prototype)

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

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

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

    115

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

    возможность гибкого управления процессом создания новых объек- тов за счет возможности динамических добавления и удаления прототипов в реестр.
    Минусы:

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

    инстанцируемые классы определяются во время выполнения, напри- мер с помощью динамической загрузки;

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

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

    116
    #include
    #include
    #include
    // Идентификаторы всех родов войск enum Warrior_ID { Infantryman_ID, Archer_ID, Horseman_ID }; class Warrior; // Опережающее объявление typedef map Registry;
    // Реестр прототипов определен в виде Singleton Мэйерса
    Registry& getRegistry()
    { static Registry _instance; return _instance;
    }
    // Единственное назначение этого класса - помощь в выборе нужного
    // конструктора при создании прототипов class Dummy { };
    // Полиморфный базовый класс. Здесь также определен статический
    // обобщенный конструктор для создания боевых единиц всех родов войск class Warrior
    { public: virtual Warrior* clone() = 0; virtual void info() = 0; virtual

    Warrior() {}
    // Параметризированный статический метод для создания воинов
    // всех родов войск static Warrior* createWarrior( Warrior_ID id ) {
    Registry& r = getRegistry(); if (r.find(id) != r.end()) return r[id]->clone(); return 0;
    } protected:
    // Добавление прототипа в множество прототипов static void addPrototype( Warrior_ID id, Warrior * prototype ) {
    Registry& r = getRegistry(); r[id] = prototype;
    }
    // Удаление прототипа из множества прототипов static void removePrototype( Warrior_ID id ) {
    Registry& r = getRegistry(); r.erase( r.find( id));
    }
    };
    // В производных классах различных родов войск в виде статических
    // членов-данных определяются соответствующие прототипы class Infantryman: public Warrior
    { public:
    Warrior* clone() { return new Infantryman( *this);
    } void info() { cout << "Infantryman" << endl;

    117
    } private:
    Infantryman( Dummy ) {
    Warrior::addPrototype( Infantryman_ID, this);
    }
    Infantryman() {} static Infantryman prototype;
    }; class Archer: public Warrior
    { public:
    Warrior* clone() { return new Archer( *this);
    } void info() { cout << "Archer" << endl;
    } private:
    Archer(Dummy) { addPrototype( Archer_ID, this);
    }
    Archer() {} static Archer prototype;
    }; class Horseman: public Warrior
    { public:
    Warrior* clone() { return new Horseman( *this);
    } void info() { cout << "Horseman" << endl;
    } private:
    Horseman(Dummy) { addPrototype( Horseman_ID, this);
    }
    Horseman() {} static Horseman prototype;
    };
    Infantryman Infantryman::prototype = Infantryman( Dummy());
    Archer Archer::prototype = Archer( Dummy());
    Horseman Horseman::prototype = Horseman( Dummy()); int main()
    { vector v; v.push_back( Warrior::createWarrior( Infantryman_ID)); v.push_back( Warrior::createWarrior( Archer_ID)); v.push_back( Warrior::createWarrior( Horseman_ID)); for(int i=0; iinfo();
    // ...
    }

    118
    12.1.5 Одиночка (Singleton)
    Одиночка (Singleton)

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

    Класс сам контролирует процесс создания единственного экземпляра.

    Паттерн легко адаптировать для создания нужного числа экземпля- ров.

    Возможность создания объектов классов, производных от Singleton.
    Минусы:

    В случае использования нескольких взаимозависимых одиночек их реализация может резко усложниться.
    Применение:
    Архитектура паттерна Singleton основана на идее использования гло- бальной переменной, имеющей следующие важные свойства:
    1. Такая переменная доступна всегда. Время жизни глобальной перемен- ной – от запуска программы до ее завершения.
    2. Предоставляет глобальный доступ, то есть, такая переменная может быть доступна из любой части программы.
    Однако, использовать глобальную переменную некоторого типа непо- средственно невозможно, так как существует проблема обеспечения един- ственности экземпляра, а именно, возможно создание нескольких перемен- ных того же самого типа (например, стековых).
    Для решения этой проблемы паттерн Singleton возлагает контроль над созданием единственного объекта на сам класс. Доступ к этому объекту осу- ществляется через статическую функцию-член класса, которая возвращает указатель или ссылку на него. Этот объект будет создан только при первом обращении к методу, а все последующие вызовы просто возвращают его ад- рес. Для обеспечения уникальности объекта, конструкторы и оператор при- сваивания объявляются закрытыми.

    119
    // Singleton.h class Singleton
    { private: static Singleton * p_instance;
    // Конструкторы и оператор присваивания недоступны клиентам
    Singleton() {}
    Singleton( const Singleton& );
    Singleton& operator=( Singleton& ); public: static Singleton * getInstance() { if(!p_instance) p_instance = new Singleton(); return p_instance;
    }
    };
    // Singleton.cpp
    #include "Singleton.h"
    Singleton* Singleton::p_instance = 0;
    Клиенты запрашивают единственный объект класса через статическую функцию-член getInstance()
    , которая при первом запросе динамически выделяет память под этот объект и затем возвращает указатель на этот уча- сток памяти. В последствии клиенты должны сами позаботиться об осво- бождении памяти при помощи оператора delete
    Последняя особенность является серьезным недостатком классической реализации шаблона Singleton. Так как класс сам контролирует создание единственного объекта, было бы логичным возложить на него ответствен- ность и за разрушение объекта. Этот недостаток отсутствует в реализации
    Singleton, впервые предложенной Скоттом Мэйерсом.
    12.2 Структурные паттерны проектирования
    Структурные паттерны рассматривают вопросы о компоновке системы на основе классов и объектов. При этом могут использоваться следующие механизмы:

    наследование, когда базовый класс определяет интерфейс, а под- классы – реализацию. Структуры на основе наследования получаются ста- тичными;

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

    адаптер (adapter);

    мост (bridge);

    120

    компоновщик (composite);

    декоратор (decorator);

    прокси (proxy).
    12.2.1 Адаптер (Adapter)
    Адаптер (Adapter)

    структурный шаблон проектирования, предназна- ченный для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс.
    Назначение:
    Часто в новом программном проекте не удается повторно использовать уже существующий код. Например, имеющиеся классы могут обладать нуж- ной функциональностью, но иметь при этом несовместимые интерфейсы. В таких случаях следует использовать паттерн Adapter.
    Паттерн Adapter, представляющий собой программную обертку над су- ществующими классами, преобразует их интерфейсы к виду, пригодному для последующего использования.
    Рассмотрим простой пример, когда следует применять паттерн Adapter.
    Пусть мы разрабатываем систему климат-контроля, предназначенной для автоматического поддержания температуры окружающего пространства в заданных пределах. Важным компонентом такой системы является темпе- ратурный датчик, с помощью которого измеряют температуру окружающей среды для последующего анализа. Для этого датчика уже имеется готовое программное обеспечение от сторонних разработчиков, представляющее собой некоторый класс с соответствующим интерфейсом. Однако использо- вать этот класс непосредственно не удастся, так как показания датчика сни- маются в градусах Фаренгейта. Нужен адаптер, преобразующий темпера- туру в шкалу Цельсия.
    Плюсы:
    Паттерн Adapter позволяет повторно использовать уже имеющийся код, адаптируя его несовместимый интерфейс к виду, пригодному для использо- вания.
    Минусы:
    Задача преобразования интерфейсов может оказаться непростой в слу- чае, если клиентские вызовы и (или) передаваемые параметры не имеют функционального соответствия в адаптируемом объекте.
    Применение:

    121
    Пусть класс, интерфейс которого нужно адаптировать к нужному виду, имеет имя
    Adaptee
    . Для решения задачи преобразования его интерфейса паттерн Adapter вводит следующую иерархию классов:

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

    производный класс
    Adapter
    , реализующий интерфейс
    Target
    . В этом классе также имеется указатель или ссылка на экземпляр
    Adaptee
    . Паттерн
    Adapter использует этот указатель для перенаправления клиентских вызовов в
    Adaptee
    . Так как интерфейсы
    Adaptee и
    Target несовместимы между со- бой, то эти вызовы обычно требуют преобразования.
    Приведем реализацию паттерна Adapter. Для примера выше адаптируем показания температурного датчика системы климат-контроля, переведя их из градусов Фаренгейта в градусы Цельсия (предполагается, что код этого датчика недоступен для модификации).
    #include
    // Уже существующий класс температурного датчика окружающей среды class FahrenheitSensor
    { public:
    // Получить показания температуры в градусах Фаренгейта float getFahrenheitTemp() { float t = 32.0;
    // ... какой то код return t;
    }
    }; class Sensor
    { public: virtual Sensor() {} virtual float getTemperature() = 0;
    }; class Adapter : public Sensor
    { public:
    Adapter( FahrenheitSensor* p ) : p_fsensor(p) {
    }
    Adapter() { delete p_fsensor;
    } float getTemperature() { return (p_fsensor->getFahrenheitTemp()-32.0)*5.0/9.0;
    } private:
    FahrenheitSensor* p_fsensor;
    };

    122 int main()
    {
    Sensor* p = new Adapter( new FahrenheitSensor); cout << "Celsius temperature = " << p->getTemperature() << endl; delete p; return 0;
    }
    12.2.2 Мост (Bridge)
    Мост (Bridge)

    шаблон проектирования, используемый в проектирова- нии программного обеспечения чтобы «разделять абстракцию и реализацию так, чтобы они могли изменяться независимо». Шаблон bridge (от англ.

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

    проще расширять систему новыми типами за счет сокращения общего числа родственных подклассов;

    возможность динамического изменения реализации в процессе вы- полнения программы;

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

    123 вые классы, которые будут реализовывать рисование в различных графиче- ских средах. При использовании такого подхода очень легко можно добав- лять, как новые фигуры, так и способы их рисования.
    Приведем реализацию логгера с применением паттерна Bridge:
    // Logger.h – Абстракция
    #include
    // Опережающее объявление class LoggerImpl; class Logger
    { public:
    Logger( LoggerImpl* p ); virtual Logger( ); virtual void log( string & str ) = 0; protected:
    LoggerImpl * pimpl;
    }; class ConsoleLogger : public Logger
    { public:
    ConsoleLogger(); void log( string & str );
    }; class FileLogger : public Logger
    { public:
    FileLogger( string & file_name ); void log( string & str ); private: string file;
    }; class SocketLogger : public Logger
    { public:
    SocketLogger( string & remote_host, int remote_port ); void log( string & str ); private: string host; int port;
    };
    // Logger.cpp – Абстракция
    #include "Logger.h"
    #include "LoggerImpl.h"
    Logger::Logger( LoggerImpl* p ) : pimpl(p)
    { }
    Logger::Logger( )
    { delete pimpl;
    }

    124
    ConsoleLogger::ConsoleLogger() : Logger(
    #ifdef MT new MT_LoggerImpl()
    #else new ST_LoggerImpl()
    #endif
    )
    { } void ConsoleLogger::log( string & str )
    { pimpl->console_log( str);
    }
    FileLogger::FileLogger( string & file_name ) : Logger(
    #ifdef MT new MT_LoggerImpl()
    #else new ST_LoggerImpl()
    #endif
    ), file(file_name)
    { } void FileLogger::log( string & str )
    { pimpl->file_log( file, str);
    }
    SocketLogger::SocketLogger( string & remote_host, int remote_port ) : Logger(
    #ifdef MT new MT_LoggerImpl()
    #else new ST_LoggerImpl()
    #endif
    ), host(remote_host), port(remote_port)
    { } void SocketLogger::log( string & str )
    { pimpl->socket_log( host, port, str);
    }
    // LoggerImpl.h – Реализация
    #include class LoggerImpl
    { public: virtual LoggerImpl( ) {} virtual void console_log( string & str ) = 0; virtual void file_log( string & file, string & str ) = 0; virtual void socket_log( tring & host, int port, string & str ) = 0;
    }; class ST_LoggerImpl : public LoggerImpl
    {

    125 public: void console_log( string & str ); void file_log ( string & file, string & str ); void socket_log ( string & host, int port, string & str );
    }; class MT_LoggerImpl : public LoggerImpl
    { public: void console_log( string & str ); void file_log ( string & file, string & str ); void socket_log ( string & host, int port, string & str );
    };
    // LoggerImpl.cpp – Реализация
    #include
    #include "LoggerImpl.h" void ST_LoggerImpl::console_log( string & str )
    { cout << "Single-threaded console logger" << endl;
    } void ST_LoggerImpl::file_log( string & file, string & str )
    { cout << "Single-threaded file logger" << endl;
    } void ST_LoggerImpl::socket_log( string & host, int port, string & str )
    { cout << "Single-threaded socket logger" << endl;
    }; void MT_LoggerImpl::console_log( string & str )
    { cout << "Multithreaded console logger" << endl;
    } void MT_LoggerImpl::file_log( string & file, string & str )
    { cout << "Multithreaded file logger" << endl;
    } void MT_LoggerImpl::socket_log( string & host, int port, string & str )
    { cout << "Multithreaded socket logger" << endl;
    }
    // Main.cpp
    #include
    #include "Logger.h" int main()
    {
    Logger * p = new FileLogger( string("log.txt"));

    126 p->log( string("message")); delete p; return 0;
    }
    Отметим несколько важных моментов приведенной реализации паттерна
    Bridge:
    1. При модификации реализации клиентский код перекомпилировать не нужно. Использование в абстракции указателя на реализацию (идиома pimpl) позволяет заменить в файле Logger.h включение include "Logger-
    Impl.h"
    на опережающее объявление class LoggerImpl
    . Такой прием сни- мает зависимость времени компиляции файла Logger.h (и, соответственно, использующих его файлов клиента) от файла LoggerImpl.h.
    2. Пользователь класса
    Logger не видит никаких деталей его реализа- ции.
    1   ...   7   8   9   10   11   12   13   14   15


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