Поиск…


Вступление

На этой странице вы можете найти примеры того, как шаблоны проектирования реализованы на C ++. Подробные сведения об этих шаблонах можно найти в документации по шаблонам проектирования .

замечания

Шаблон проектирования является общим многоразовым решением общей проблемы в данном контексте при разработке программного обеспечения.

Схема наблюдателя

Целью Observer Pattern является определение зависимости один-на-один между объектами, чтобы при изменении состояния одного объекта все его иждивенцы были уведомлены и обновлены автоматически.

Субъект и наблюдатели определяют отношения «один ко многим». Наблюдатели зависят от предмета, так что, когда состояние субъекта изменяется, наблюдатели получают уведомление. В зависимости от уведомления наблюдатели могут также обновляться новыми значениями.

Вот пример из книги «Шаблоны проектирования» Гамма.

#include <iostream>
#include <vector>

class Subject; 

class Observer 
{ 
public:
    virtual ~Observer() = default;
    virtual void Update(Subject&) = 0;
};

class Subject 
{ 
public: 
     virtual ~Subject() = default;
     void Attach(Observer& o) { observers.push_back(&o); }
     void Detach(Observer& o)
     {
         observers.erase(std::remove(observers.begin(), observers.end(), &o));
     }
     void Notify()
     {
         for (auto* o : observers) {
             o->Update(*this);
         }
     }
private:
     std::vector<Observer*> observers; 
};

class ClockTimer : public Subject 
{ 
public:

    void SetTime(int hour, int minute, int second)
    {
        this->hour = hour; 
        this->minute = minute;
        this->second = second;

        Notify(); 
    }

    int GetHour() const { return hour; }
    int GetMinute() const { return minute; }
    int GetSecond() const { return second; }

private: 
    int hour;
    int minute;
    int second;
}; 

class DigitalClock: public Observer 
{ 
public: 
     explicit DigitalClock(ClockTimer& s) : subject(s) { subject.Attach(*this); }
     ~DigitalClock() { subject.Detach(*this); }
     void Update(Subject& theChangedSubject) override
     {
         if (&theChangedSubject == &subject) {
             Draw();
         }
     }

     void Draw()
     {
         int hour = subject.GetHour(); 
         int minute = subject.GetMinute(); 
         int second = subject.GetSecond(); 

         std::cout << "Digital time is " << hour << ":" 
                   << minute << ":" 
                   << second << std::endl;           
     }

private:
     ClockTimer& subject;
};

class AnalogClock: public Observer 
{ 
public: 
     explicit AnalogClock(ClockTimer& s) : subject(s) { subject.Attach(*this); }
     ~AnalogClock() { subject.Detach(*this); }
     void Update(Subject& theChangedSubject) override
     {
         if (&theChangedSubject == &subject) {
             Draw();
         }
     }
     void Draw()
     {
         int hour = subject.GetHour(); 
         int minute = subject.GetMinute(); 
         int second = subject.GetSecond(); 

         std::cout << "Analog time is " << hour << ":" 
                   << minute << ":" 
                   << second << std::endl; 
     }
private:
     ClockTimer& subject;
};

int main()
{ 
    ClockTimer timer; 

    DigitalClock digitalClock(timer); 
    AnalogClock analogClock(timer); 

    timer.SetTime(14, 41, 36);
}

Выход:

Digital time is 14:41:36
Analog time is 14:41:36

Вот краткое описание шаблона:

  1. Объекты (объект DigitalClock или AnalogClock ) используют интерфейсы Subject ( Attach() или Detach() ) либо для подписания (регистрации) в качестве наблюдателей, либо для subject.Attach(*this); от подписки (удаления) от наблюдателей ( subject.Attach(*this); subject.Detach(*this);

  2. У каждого субъекта может быть много наблюдателей ( vector<Observer*> observers; ).

  3. Все наблюдатели должны реализовать интерфейс Observer. Этот интерфейс имеет только один метод Update() , который вызывается при изменении состояния Subject ( Update(Subject &) )

  4. В дополнение к методам Attach() и Detach() конкретный объект реализует метод Notify() который используется для обновления всех текущих наблюдателей всякий раз, когда изменяется состояние. Но в этом случае все они выполняются в родительском классе Subject ( Subject::Attach (Observer&) , void Subject::Detach(Observer&) и void Subject::Notify() .

  5. Объект Concrete также может иметь методы настройки и получения состояния.

  6. Конкретными наблюдателями могут быть любые классы, реализующие интерфейс Observer. Каждый наблюдатель подписывается (регистрируется) конкретным субъектом для получения обновлений ( subject.Attach(*this); ).

  7. Два объекта шаблона наблюдателя слабо связаны друг с другом, они могут взаимодействовать, но не знают друг друга.

Вариация:

Сигнал и слоты

Сигналы и слоты - это языковая конструкция, введенная в Qt, что упрощает реализацию шаблона Observer, избегая при этом шаблона кода. Концепция состоит в том, что элементы управления (также известные как виджеты) могут отправлять сигналы, содержащие информацию о событиях, которые могут быть получены другими элементами управления, с использованием специальных функций, известных как слоты. Слот в Qt должен быть членом класса, объявленным как таковой. Система сигналов / слотов хорошо подходит для использования графических пользовательских интерфейсов. Аналогично, система сигнала / слота может использоваться для асинхронного ввода / вывода (включая сокеты, каналы, последовательные устройства и т. Д.) Уведомления о событиях или для связывания событий таймаута с соответствующими экземплярами объектов и методами или функциями. Нет необходимости записывать код регистрации / регистрации / вызова, поскольку компилятор метаданных Qt (MOC) автоматически генерирует необходимую инфраструктуру.

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

Шаблон адаптера

Преобразуйте интерфейс класса в другой интерфейс. Адаптер (или Wrapper) позволяет работать вместе, что в противном случае не может быть связано с несовместимыми интерфейсами. Мотивация шаблона адаптера заключается в том, что мы можем повторно использовать существующее программное обеспечение, если мы можем изменить интерфейс.

  1. Шаблон адаптера зависит от состава объекта.

  2. Клиент вызывает операцию на объекте адаптера.

  3. Адаптер вызывает Adaptee для выполнения операции.

  4. В STL стек адаптируется из вектора: когда стек выполняет push (), базовый вектор делает vector :: push_back ().

Пример:

#include <iostream>

// Desired interface (Target)
class Rectangle 
{
  public:
    virtual void draw() = 0;
};

// Legacy component (Adaptee)
class LegacyRectangle 
{
  public:
    LegacyRectangle(int x1, int y1, int x2, int y2) {
        x1_ = x1;
        y1_ = y1;
        x2_ = x2;
        y2_ = y2;
        std::cout << "LegacyRectangle(x1,y1,x2,y2)\n";
    }
    void oldDraw() {
        std::cout << "LegacyRectangle:  oldDraw(). \n";
    }
  private:
    int x1_;
    int y1_;
    int x2_;
    int y2_;
};

// Adapter wrapper
class RectangleAdapter: public Rectangle, private LegacyRectangle 
{
  public:
    RectangleAdapter(int x, int y, int w, int h):
      LegacyRectangle(x, y, x + w, y + h) {
         std::cout << "RectangleAdapter(x,y,x+w,x+h)\n";
      }

    void draw() {
        std::cout << "RectangleAdapter: draw().\n"; 
        oldDraw();
    }
};

int main()
{
  int x = 20, y = 50, w = 300, h = 200;
  Rectangle *r = new RectangleAdapter(x,y,w,h);
  r->draw();
}

//Output:
//LegacyRectangle(x1,y1,x2,y2)
//RectangleAdapter(x,y,x+w,x+h)

Резюме кода:

  1. Клиент думает, что он разговаривает с Rectangle

  2. Цель - это класс Rectangle . Это то, на что клиент вызывает метод.

     Rectangle *r = new RectangleAdapter(x,y,w,h);
     r->draw();
    
  3. Обратите внимание, что класс адаптера использует множественное наследование.

     class RectangleAdapter: public Rectangle, private LegacyRectangle {
         ...
     }
    
  4. Адаптер RectangleAdapter позволяет LegacyRectangle на запрос ( draw() на Rectangle ), наследуя классы BOTH.

  5. Класс LegacyRectangle не имеет одинаковых методов ( draw() ) как Rectangle , но Adapter(RectangleAdapter) может принимать вызовы метода Rectangle и обходить и вызывать метод в LegacyRectangle , oldDraw() .

     class RectangleAdapter: public Rectangle, private LegacyRectangle {
       public:
         RectangleAdapter(int x, int y, int w, int h):
           LegacyRectangle(x, y, x + w, y + h) {
             std::cout << "RectangleAdapter(x,y,x+w,x+h)\n";
           }
    
         void draw() {
             std::cout << "RectangleAdapter: draw().\n"; 
             oldDraw();
         }
     };
    

Шаблон проектирования адаптера переводит интерфейс для одного класса в совместимый, но другой интерфейс. Таким образом, это похоже на шаблон прокси-сервера , поскольку это однокомпонентная оболочка. Но интерфейс для класса адаптера и исходного класса может быть другим.

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

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

Заводской шаблон

Factory pattern отделяет создание объекта и позволяет создавать по имени с использованием общего интерфейса:

class Animal{
public:
    virtual std::shared_ptr<Animal> clone() const = 0;
    virtual std::string  getname() const = 0;
};

class Bear: public Animal{
public:
    virtual std::shared_ptr<Animal> clone() const override
    {
        return std::make_shared<Bear>(*this);
    }
    virtual std::string getname() const override
    {
        return "bear";
    }
};


class Cat: public Animal{
public:
    virtual std::shared_ptr<Animal> clone() const override
    {
        return std::make_shared<Cat>(*this);
    }
    virtual std::string  getname() const override
    {
        return "cat";
    }
};

class AnimalFactory{
public:
    static std::shared_ptr<Animal> getAnimal( const std::string&   name )
    {
      if ( name == "bear" )
        return std::make_shared<Bear>();
      if ( name == "cat" )
        return std::shared_ptr<Cat>();
     
    return nullptr;
    }


};

Шаблон Builder с Fluent API

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

Вдохновленный разработчиком электронной почты в C # , я решил создать версию на C ++. Объект электронной почты не обязательно является очень сложным объектом , но он может демонстрировать шаблон.

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

// Forward declaring the builder
class EmailBuilder;

class Email
{
  public:
    friend class EmailBuilder;  // the builder can access Email's privates
    
    static EmailBuilder make();
    
    string to_string() const {
        stringstream stream;
        stream << "from: " << m_from
               << "\nto: " << m_to
               << "\nsubject: " << m_subject
               << "\nbody: " << m_body;
        return stream.str();
    }
    
  private:
    Email() = default; // restrict construction to builder
    
    string m_from;
    string m_to;
    string m_subject;
    string m_body;
};

class EmailBuilder
{
  public:
    EmailBuilder& from(const string &from) {
        m_email.m_from = from;
        return *this;
    }
    
    EmailBuilder& to(const string &to) {
        m_email.m_to = to;
        return *this;
    }
    
    EmailBuilder& subject(const string &subject) {
        m_email.m_subject = subject;
        return *this;
    }
    
    EmailBuilder& body(const string &body) {
        m_email.m_body = body;
        return *this;
    }
    
    operator Email&&() {
        return std::move(m_email); // notice the move
    }
    
  private:
    Email m_email;
};

EmailBuilder Email::make()
{
    return EmailBuilder();
}

// Bonus example!
std::ostream& operator <<(std::ostream& stream, const Email& email)
{
    stream << email.to_string();
    return stream;
}


int main()
{
    Email mail = Email::make().from("[email protected]")
                              .to("[email protected]")
                              .subject("C++ builders")
                              .body("I like this API, don't you?");
                              
    cout << mail << endl;
}

Для более старых версий C ++ можно просто игнорировать операцию std::move и удалить && из оператора преобразования (хотя это создаст временную копию).

Строитель завершает свою работу, когда он отправляет построенное электронное письмо operator Email&&() . В этом примере строитель является временным объектом и возвращает сообщение перед его уничтожением. Вы также можете использовать явную операцию, например Email EmailBuilder::build() {...} вместо оператора преобразования.

Пропустите строитель вокруг

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

void add_addresses(EmailBuilder& builder)
{
    builder.from("[email protected]")
           .to("[email protected]");
}

void compose_mail(EmailBuilder& builder)
{
    builder.subject("I know the subject")
           .body("And the body. Someone else knows the addresses.");
}

int main()
{
    EmailBuilder builder;
    add_addresses(builder);
    compose_mail(builder);
    
    Email mail = builder;
    cout << mail << endl;
}

Вариант исполнения: Mutable object

Вы можете изменить дизайн этого шаблона в соответствии с вашими потребностями. Я дам один вариант.

В данном примере объект электронной почты является неизменным, т. Е. Его свойства не могут быть изменены, поскольку к ним нет доступа. Это была желаемая особенность. Если вам нужно изменить объект после его создания, вы должны предоставить ему некоторые сеттеры. Поскольку эти сеттеры будут дублироваться в построителе, вы можете рассмотреть возможность сделать все это в одном классе (больше не нужен строительный класс). Тем не менее, я бы подумал о необходимости сделать встроенный объект изменчивым в первую очередь.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow