SoftCraft
разноликое программирование

Top.Mail.Ru

Процедурно-параметрическая парадигма и паттерны ОО проектирования

© 05.07.2026
Александр Легалов


Содержание


Наблюдатель (Observer)

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

  • у абстракции есть два аспекта, один из которых зависит от другого. Инкапсуляции этих аспектов в разные объекты позволяют изменять и повторно использовать их независимо;
  • при модификации одного объекта требуется изменить другие и заранее неизвестно, сколько именно объектов нужно изменить;
  • один объект должен оповещать других, не делая предположений об уведомляемых объектах. То есть, объекты не должны быть тесно связаны между собой.

ОО структура Наблюдателя приведено на рисунке.

Структура паттерна Наблюдатель

Структура паттерна Наблюдатель

Объектно-ориентированная реализация Наблюдателя

Основная идея образца заключается в том, что субъект располагает информацией о своих наблюдателях. При этом за ним может «следить» любое число наблюдателей. Субъект предоставляет интерфейс для присоединения и отделения наблюдателей, которые определяет интерфейс обновления для объектов, которые должны уведомляться об изменении субъекта. На основе интерфейсов Субъектов формируются Конкретные субъекты которые сохраняют состояние, представляющее интерес для конкретных наблюдателей, и посылают информацию своим наблюдателям, когда происходит изменение этого состояния;

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


  //------------------------------------------------------------------------------
  // Абстрактный класс, определяющий базовый интерфейс субъекта
  class ISubject { // Интерфейс субьекта
    public:
    virtual ~ISubject(){};
    virtual void Attach(IObserver *observer) = 0;
    virtual void Detach(IObserver *observer) = 0;
    virtual void Notify() = 0;
  };

Интерфейс наблюдателя содержит чистый метод для обновления, через который осуществляется связь с субъектами:


  //------------------------------------------------------------------------------
  class IObserver { // Интерфейс наблюдателя
    public:
    virtual ~IObserver(){};
    virtual void Update() = 0;
  };

Реализуемый далее Конкретный субьект, осуществляет хранение и изменение пары целых чисел. При их изменени, осуществляемом посредством метода Set, он может уведомлять своих наблюдателей, ссылки на которые хранятся в списке, о прошедших изменениях используя метод Notify. Он также, по запросу от наблюдателя, обращающегося к методу Get, предоставляет результат сложения этих двух аргументов.


  //------------------------------------------------------------------------------
  // Субьект, осуществляющий хранение целых чисел и уведомляющией своих
  // наблюдателей по мере своих данных. Он также предоставляет наблюдателю
  // по его запросу результат сложения двух аргументов
  class AddSubject : public ISubject {
    public:
    virtual ~AddSubject() {
      std::cout << "Goodbye, I was the Subject.\n";
    }

    // Методы управления подпиской.
    void Attach(IObserver *observer) override { // присоединение наблюдателя
      list_observer_.push_back(observer);
    }
    void Detach(IObserver *observer) override { // отсоединение наблюдателя
      list_observer_.remove(observer);
    }
    void Notify() override {  // уведомление наблюдателя об изменении
      std::list<IObserver *>::iterator iterator = list_observer_.begin();
      HowManyObservers();
      while (iterator != list_observer_.end()) {
        (*iterator)->Update();
        ++iterator;
      }
    }

    void HowManyObservers() { // число присоединенных наблюдателей
      std::cout << "There are " << list_observer_.size() << " observers in the list.\n";
    }

    // Изменение значений в субъекте
    void Set(int v1, int v2) {
      x1 = v1;
      x2 = v2;
      Notify();   // Уведомление об изменении
    }
    // Получение значения, предоставляемого субъектом
    int Get() {
      std::cout << x1 << " + " << x2 << " = ";
      return x1 + x2;
    }
    // Информация о субъекте
    void PrintInfo() {
      std::cout << "   Subject is Addition;\n";
    }
    private:
    std::list<IObserver *> list_observer_; // список наблюдателей
    // целые числа, определяющие субъекта
    int x1;
    int x2;
  };

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


  //------------------------------------------------------------------------------
  // Наблюдатель, получающий целое число от соответствующих субъектов
  // и осуществляющий его вывод в десятичной системе счисления.
  class DecObserver : public IObserver {
    public:
    // Заточен на AddSubject, так как использует доступ
    // к невиртуальному методу Get
    DecObserver(AddSubject &subject) : subject_(subject) {
      subject_.Attach(this); // наблюдатель цепляется к полученному субъекту
    }
    virtual ~DecObserver() {
      std::cout << "Goodbye, I was the DecObserver.\n";
    }

    void Update() override {
      // Addition->Decimal: 32 + 10 = 42
      std::cout << "Addition->Decimal: " << std::dec << subject_.Get() << "\n";
      subject_.PrintInfo();
      PrintInfo();
    }
    void RemoveMeFromTheList() {
      subject_.Detach(this);
      std::cout << "DecObserver removed from the list.\n";
    }
    void PrintInfo() {
      std::cout << "   Observer is Decimal;\n";
    }
    private:
    AddSubject &subject_;
  };

  //------------------------------------------------------------------------------
  // Наблюдатель, получающий целое число от соответствующих субъектов
  // и осуществляющий его вывод в шестнадцатиричной системе счисления.
  class HexObserver : public IObserver {
    public:
    // Заточен на AddSubject, так как использует доступ
    // к невиртуальному методу Get
    HexObserver(AddSubject &subject) : subject_(subject) {
      subject_.Attach(this); // наблюдатель цепляется к полученному субъекту
    }
    virtual ~HexObserver() {
      std::cout << "Goodbye, I was the HexObserver.\n";
    }

    void Update() override {
      std::cout << "Addition->Hex: " << std::hex << subject_.Get() << "\n";
      subject_.PrintInfo();
      PrintInfo();
    }
    void RemoveMeFromTheList() {
      subject_.Detach(this);
      std::cout << "HexObserver removed from the list.\n";
    }
    void PrintInfo() {
      std::cout << "   Observer is Hex;\n";
    }
    private:
    AddSubject &subject_;
  };

Клиентский код и главная функция используются для демонстрации одного из вариантов использования представленного ОО образца.


  void ClientCode() {
    AddSubject addSubject;
    DecObserver decObserver(addSubject);
    HexObserver hexObserver(addSubject);
    addSubject.Set(10, 32);
    addSubject.Set(-15, 10);
    addSubject.Set(10, 3);
    decObserver.RemoveMeFromTheList();
    addSubject.Set(100, 300);
  }

  int main() {
    ClientCode();
    return 0;
  }

Обобщенная схема Наблюдателя

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

Обобщенная структура Наблюдателя>

Обобщенная структура Наблюдателя

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

  • Классический MVC (Passive Model): Контроллер обновляет Модель и передает управление Виду. Модель ничего не знает о других компонентах, из-за чего Вид должен напрямую опрашивать Модель для отображения состояния.
  • Активный MVC (Active Model / Наблюдатель): Использует образец Наблюдатель (Observer). Модель содержит список подписчиков (Видов) и самостоятельно оповещает их при изменении данных, что делает интерфейс независимым от деталей реализации Модели.
  • MVP (Model-View-Presenter): Представление (Вид) берет на себя роль вывода данных, а бизнес-логика выносится в _Presenter_ (Аналог контроллера, но он жестко привязан к интерфейсу и берет на себя трансформацию данных для отображения).
  • MVVM (Model-View-ViewModel): Разновидность, созданная для упрощения синхронизации данных. Связующим звеном выступает ViewModel, которая предоставляет интерфейсы для передачи данных между Моделью и Видом, часто используя концепцию двусторонней привязки данных (Data Binding).
  • HMVC (Hierarchical MVC): Приложение разбивается на иерархическое дерево независимых троек MVC (виджеты, модули). Один контроллер может управлять дочерними MVC-триадами, что удобно для сложных масштабируемых интерфейсов.

Процедурно-параметрическая реализация Наблюдателя

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

Процедурная схема паттерна Наблюдатель>

Процедурная схема паттерна Наблюдатель

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


  //------------------------------------------------------------------------------
  // Обобщенный субъект ссылается на подключаемые к нему наблюдатели
  typedef struct Subject {
    List listObserver;  // Список доступных наблюдателей
  }<> Subject;

В связи с тем, что список наблюдателей, подписанных на конкретного субъекта в данном случае реализован программно (без использования библиотеки), он будет выглядеть следующим образом.


  // Предварительное описание для использования в качестве указателя
  typedef struct Observer Observer;
  // Список для наблюдателей. Создается как замена ОО класса list
  typedef struct ListElement { // Элемент списка
    Observer* observer;
    struct ListElement* next;
  } ListElement;

  typedef struct List { // Голова списка
    ListElement* first;
    ListElement* last;
  } List;

  //==============================================================================
  // Описание интерфейса для работы со списком обобщенных наблюдателей
  //==============================================================================
  // Начальная инициализация списка
  void ListInit(List* list) {
    list->first = list->last = NULL;
  }
  // Добавление наблюдателя в конец списка
  void ListPushBack(List* list, Observer* observer) {
    ListElement* element = malloc(sizeof(ListElement));
    element->observer = observer;
    element->next = NULL;
    if(list->first == NULL) {
      list->first = element;
      list->last = element;
    } else {
      list->last->next = element;
      list->last = element;
    }
  }

  //------------------------------------------------------------------------------
  int ListSize(List* list) { // Определение длины списка
    int count = 0;
    ListElement* e = list->first;
    while(e != NULL) {
      ++count;
      e = e->next;
    }
    return count;
  }
  //------------------------------------------------------------------------------
  // Поиск и удаление наблюдателя из списка по совпадению указателей
  void ListRemove(List* list, Observer* observer) {
    ListElement* e = list->first;
    ListElement* pre = NULL;
    while(e != NULL) {
      if(e->observer == observer) {
        if(e == list->first) { // первый в списке
          // Если единственный в спискe
          if(list->first == list->last) {
            list->first = list->last = NULL;
            free(e);
            return;
          }
          list->first = e->next;
          free(e);
          return;
        }
        if(list->last == e) {
          list->last = pre;
        }
        pre->next = e->next;
        free(e);
        return;
      }
      pre = e;
      e = e->next;
    }
  }

Обобщенный наблюдатель не содержить общих данных и используется для дальнейшего расширения конкретными наблюдателями.


  //------------------------------------------------------------------------------
  // Обобщенный наблюдатель косвенно через обработчики связывается с субъектами.
  // Общих данных нет, но могут быть в более сложной реализации.
  typedef struct Observer {}<> Observer;

Взаимодействие с субъектом, обеспечивающее все функции по его инициализации, подключению наблюдателей осуществляет через обычные функции.


  //==============================================================================
  // Описание функций, определяющих работу субъекта
  //==============================================================================

  // Инициализация обобщенного субъекта
  void SubjectInit(Subject* subject) {
    ListInit(&subject->listObserver);
  }
  //------------------------------------------------------------------------------
  // Подписка субъекта на заданного наблюдателя
  void Attach(Subject* subject, Observer *observer) {
    ListPushBack(&(subject->listObserver), observer);
  }
  //------------------------------------------------------------------------------
  // Отсоединение наблюдателя от списка заданного субъекта
  void Detach(Subject* subject, Observer *observer) {
    ListRemove(&(subject->listObserver), observer);
  }
  //------------------------------------------------------------------------------
  // Удаление наблюдателя из динамической памяти по указателю
  void SubjectDelete(Subject* subject) {
    printf("Goodbye, I was the Subject.\n");
    free(subject);
  }
  //------------------------------------------------------------------------------
  //  Информация о числе наблюдателей за субъектом (не знаю, зачем)
  void HowManyObservers(Subject* subject) {
    printf("Observers in the list: %d\n",
          ListSize(&(subject->listObserver)));
  }

Функция NotifyAllObservers обеспечивет обход всех наблюдателей конкретного субъекта, передаваемого в качестве аргумента. Для каждой пары субъект - наблюдатель при этом вызывается полиморфная функция GetAndUpdate, осуществляющая все действия по обновлению наблюдателя в соответствии с данными, хранящимися в субъекте. То есть, в одной этой функции реализуется цепочка трех методов Notify-Update-Get описанной выше ОО программы. Полиморфные функции PrintSubjectInfo и PrintObserverInfo выводят тестовую информацию об используемых специализациях субъекта и наблюдателя.


  //------------------------------------------------------------------------------
  // Прототипы обобщенных функций, используемых при обработке взаимодействий
  // субъектов и наблюдателей
  void GetAndUpdate<Subject* subject, Observer* observer>();
  void PrintSubjectInfo<Subject* subject>();
  void PrintObserverInfo<Observer* observer>();

  // Функция, осуществляющая обход наблюдателей и выполнение в них нужных
  // манипуляций
  void NotifyAllObservers(Subject* subject) {
    HowManyObservers(subject);
    // Обход субьектом своих наблюдателей
    ListElement* e = subject->listObserver.first;
    while(e != NULL) {
      // и передача ему необходимой информации через обобщенную функцию
      GetAndUpdate<subject, e->observer>();
      PrintSubjectInfo<subject>();
      PrintObserverInfo<e->observer>();
      e = e->next;
    }
  }

  // Обобщенная функция, задающая обработку данных субъекта в наблюдателе
  // Функции субъекта и наблюдателя реализованы непосредственно, так как
  // простые. Но могут предоставляться как отдельный функционал.
  void GetAndUpdate<Subject* subject, Observer* observer>() {
    // Нельзя использовать обобщения
    printf("Incorrect Combination of Subject and Observer:\n");
    // PrintSubjectInfo<subject>();  // Вывод информации о специализации субьекта
    // PrintObserverInfo<observer>(); // Вывод информации о специализации наблюдателя
    // exit(13);
  }
  //------------------------------------------------------------------------------
  // Вывод информации об обобщенном субьекте
  void PrintSubjectInfo<Subject* subject>() {
    printf("   Subject is Generalization;\n");
  }
  //------------------------------------------------------------------------------
  // Вывод информации о специализации наблюдателя
  void PrintObserverInfo<Observer* observer>() {
    printf("   Observer is Generalization;\n");
  }

Все описанные выше полиморфные обработчики обобщений эволюционно расширяются по мере добавления новых обработчиков специализаций. При этом обобщеннай функция
GetAndUpdate<Subject* subject, Observer* observer>() в тестовом режиме только информирует о том, что используется некорректная комбинация субъекта и специализации. В реальных программах возможно прерывание процесса выполнения, что и отображено в закомментированной части функции.

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


  // Для субъектов, использующих только целые типы, одна основа и разны признаки
  typedef struct IntIntBaseSpec {int x1, x2;} IntIntBaseSpec;
  //------------------------------------------------------------------------------
  // Функция сложения над основой
  int Add(IntIntBaseSpec* opd) {
    return opd->x1 + opd->x2;
  }

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


  // Специализация субьекта, используемая для сложения
  Subject + <add: IntIntBaseSpec;>;
  //------------------------------------------------------------------------------
  // Вывод информации о специализации сложения
  void PrintSubjectInfo<Subject.add* subject>() {
    printf("   Subject is Addition;\n");
  }
  //------------------------------------------------------------------------------
  // Инициализация специализации сложения
  void AddSubjectInit(Subject.add* subject) {
    ListInit(&subject->listObserver);
    subject->@x1 = 0;
    subject->@x2 = 0;
  }
  //------------------------------------------------------------------------------
  // Изменение данных в специализации сложения
  void AddSubjectSet(Subject.add* subject, int x1, int x2) {
    subject->@x1 = x1;
    subject->@x2 = x2;
  }
  //------------------------------------------------------------------------------
  // Изменение данных в специализации сложения и обновление наблюдателей
  void AddSubjectSetAndNotify(Subject.add* subject, int x1, int x2) {
    AddSubjectSet(subject, x1, x2);
    NotifyAllObservers(subject);
  }

Для демонстрации различных возможностей приведены две функции установки значений. Функция AddSubjectSet только устанавливает новые значения, а AddSubjectSetAndNotify помимо этого запускает обновление наблюдателей, связанных с переменной, используемой в качестве первого параметра. Помимо этого функцию NotifyAllObservers, выполняющую обновление наблюдателей, можно использовать при необходимости и без изменения субъекта.

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


  //==============================================================================
  // Специализации наблюдателей
  //==============================================================================

  // Специализация наблюдателя, используемая для вывода в десятичном виде.
  // Для нашей простой реализации достаточно эволюционно расширяемого
  // перечислимого типа.
  Observer + <decimal: void;>;
  //------------------------------------------------------------------------------
  // Вывод информации о десятичном наблюдателе
  void PrintObserverInfo<Observer.decimal* observer>() {
    printf("   Observer is Decimal;\n");
  }
  //------------------------------------------------------------------------------
  // Еще одна специализация наблюдателя, для вывода в шестнадцатиричном виде
  Observer + <hex: void;>;
  //------------------------------------------------------------------------------
  // Вывод информации о шестнадцатиричном наблюдателе
  void PrintObserverInfo<Observer.hex* observer>() {
    printf("   Observer is Hex;\n");
  }
  //------------------------------------------------------------------------------
  // Cпециализация наблюдателя, для вывода с плавающей точкой двойной точности
  Observer + <dbl: void;>;
  //------------------------------------------------------------------------------
  // Вывод информации о шестнадцатиричном наблюдателе
  void PrintObserverInfo<Observer.dbl* observer>() {
    printf("   Observer is Double;\n");
  }

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


  //==============================================================================
  // Мультиметод, формирующий отношения между специализациями
  //==============================================================================

  // Обработка комбинации Сложение - десятичный наблюдатель
  void GetAndUpdate<Subject.add* subject, Observer.decimal* observer>() {
    // Формирование суммы связано с субьектом.
    int result = Add(&(subject->@));
    // Вывод в десятичном виде связан с наблюдателем
    printf("Addition->Decimal: %d + %d = %d\n", subject->@x1, subject->@x2, result);
  }
  //------------------------------------------------------------------------------
  // Обработка комбинации Сложение - шестнадцатиричный наблюдатель
  void GetAndUpdate<Subject.add* subject, Observer.hex* observer>() {
    // Формирование суммы связано с субьектом.
    int result = Add(&(subject->@));
    // Вывод в десятичном виде связан с наблюдателем
    printf("Addition->Hex: 0x%X + 0x%X = 0x%X\n", subject->@x1, subject->@x2, result);
  }
  //------------------------------------------------------------------------------
  // Пусть для числа с плавающей точкой отсутствует специальный обработчик
  // ...

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


  //------------------------------------------------------------------------------
  // Еще одна специализация субьекта, используемая для деления
  Subject + <div: IntIntBaseSpec;>;
  //------------------------------------------------------------------------------
  // Вывод информации о специализации деления
  void PrintSubjectInfo<Subject.div* subject>() {
    printf("   Subject is Divide;\n");
  }
  //------------------------------------------------------------------------------
  // Инициализация специализации деления
  void DivSubjectInit(Subject.div* subject) {
    ListInit(&subject->listObserver);
    subject->@x1 = 0;
    subject->@x2 = 0;
  }

  //------------------------------------------------------------------------------
  // Изменение данных в специализации деления
  void DivSubjectSet(Subject.div* subject, int x1, int x2) {
    subject->@x1 = x1;
    subject->@x2 = x2;
  }

  //------------------------------------------------------------------------------
  // Изменение данных в специализации деления и обновление наблюдателей
  void DivSubjectSetAndNotify(Subject.div* subject, int x1, int x2) {
    DivSubjectSet(subject, x1, x2);
    NotifyAllObservers(subject);
  }

  //------------------------------------------------------------------------------
  // После этого можно дописать используемые им наблюдатели
  //------------------------------------------------------------------------------
  // Наблюдатель, использующий целочисленный десятичный вывод
  void GetAndUpdate<Subject.div* subject, Observer.decimal* observer>() {
    // Непосредственной формирование  деления. Хотя можно использовать и
    // функцию для основы
    // Вычисления упрощены. Можно сделать и более солидную обработку, выделив
    // все операции в отдельную функцию.
    int result = 0;
    int flag = 0;
    if(subject->@x2 == 0) {
      if(subject->@x1 == 0) {
        flag = -1;
      } else flag = -2;
    } else {
      result = subject->@x1 / subject->@x2;
    }

    // А здесь начинается работа наблюдателя
    if(flag == 0) {
      printf("Division->Decimal: %d / %d = %d\n", subject->@x1, subject->@x2, result);
    } else if(flag == -1) {
      printf("Division->Decimal: %d / %d = Any value\n", subject->@x1, subject->@x2);
    } else if(flag == -2) {
      printf("Division->Decimal: %d / %d = Zero divide situation\n", subject->@x1, subject->@x2);
    } else {
      printf("Division->Decimal: %d / %d = An unaccounted-for situation\n", subject->@x1, subject->@x2);
    }
  }

  //------------------------------------------------------------------------------
  // Наблюдатель, использующий вывод с плавающей точкой
  void GetAndUpdate<Subject.div* subject, Observer.dbl* observer>() {
    // Непосредственной формирование  деления. Хотя можно использовать и
    // функцию для основы
    // Вычисления упрощены. Можно сделать и более солидную обработку, выделив
    // все операции в отдельную функцию.
    double result = 0.0;
    int flag = 0;
    if(subject->@x2 == 0) {
      if(subject->@x1 == 0) {
        flag = -1;
      } else flag = -2;
    } else {
      result = (double)subject->@x1 / (double)subject->@x2;
    }

    // А здесь начинается работа наблюдателя
    if(flag == 0) {
      printf("Division->Double: %d / %d = %lf\n", subject->@x1, subject->@x2, result);
    } else if(flag == -1) {
      printf("Division->Double: %d / %d = Any value\n", subject->@x1, subject->@x2);
    } else if(flag == -2) {
      printf("Division->Double: %d / %d = Zero divide situation\n", subject->@x1, subject->@x2);
    } else {
      printf("Division->Double: %d / %d = An unaccounted-for situation\n", subject->@x1, subject->@x2);
    }
  }

Возможность создания полиморфных функций вне обрабатываемых ими данных позволяет изменять функциональность программ без модификации ранее написанного кода. В качестве примера дальнейшего расширения реализована функция подписки, наблюдателей, в которой до добавлнеия наблюдателя осуществляется проверка на его совместимость. Реализация, как и процесс использования наблюдателей, осуществляется через полиморфный мультиметод. Функция isCorrect по умолчанию возвращает ложь, порождая истинное значение только для разрешенных комбинаций. Это позволяет избавиться от добавления некорректных наблюдателей в функции VerifyAndAttach.


  //==============================================================================
  // Дополнительная проверка совместимости субъекта и наблюдателя
  //==============================================================================

  // Обобщенная функция, сигнализирующая о несовместимости.
  _Bool isCorrect<Subject* subject, Observer* observer>() {
    printf("Attach control: Incorrect combination for Attachment\n");
    PrintSubjectInfo<subject>();
    PrintObserverInfo<observer>();
    // exit(13);
    return 0;
  }
  //------------------------------------------------------------------------------
  // Совместимость для пары Addition->Decimal
  _Bool isCorrect<Subject.add* subject, Observer.decimal* observer>() {
    printf("Attach control: Correct combination Addition->Decimal for Attachment\n");
    PrintSubjectInfo<subject>();
    PrintObserverInfo<observer>();
    return 1;
  }
  //------------------------------------------------------------------------------
  // Совместимость для пары Addition->Hex
  _Bool isCorrect<Subject.add* subject, Observer.hex* observer>() {
    printf("Attach control: Correct combination Addition->Неx for Attachment\n");
    PrintSubjectInfo<subject>();
    PrintObserverInfo<observer>();
    return 1;
  }
  //------------------------------------------------------------------------------
  // Совместимость для пары Division->Decimal
  _Bool isCorrect<Subject.div* subject, Observer.decimal* observer>() {
    printf("Attach control: Correct combination Division->Decimal for Attachment\n");
    PrintSubjectInfo<subject>();
    PrintObserverInfo<observer>();
    return 1;
  }
  //------------------------------------------------------------------------------
  // Совместимость для пары Division->Double
  _Bool isCorrect<Subject.div* subject, Observer.dbl* observer>() {
    printf("Attach control: Correct combination Division->Doble for Attachment\n");
    PrintSubjectInfo<subject>();
    PrintObserverInfo<observer>();
    return 1;
  }
  //------------------------------------------------------------------------------
  // Подписка субъекта на заданного наблюдателя с предварительной проверкой
  // на допустимость соединени. Если такое соединение недопустимо, то подписка
  // не осуществляется
  void VerifyAndAttach(Subject* subject, Observer *observer) {
    if(isCorrect<subject, observer>()) {
      ListPushBack(&(subject->listObserver), observer);
    }
    // else {exit(13);}
  }

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


  //==============================================================================
  // Клиентский код, осуществляющий взаимодействие субъектов и наблюдателей
  //==============================================================================

  void ClientCode() {
    // Subject *subject = malloc(sizeof(Subject));
    Subject.add addSubject;
    AddSubjectInit(&addSubject);
    Observer.decimal decimalObserver;
    Attach(&addSubject, &decimalObserver);
    Observer.hex hexObserver;
    Attach(&addSubject, &hexObserver);
    Observer.dbl dblObserver;
    Attach(&addSubject, &dblObserver);
    // Изменение данных и обновление наблюдателей
    AddSubjectSetAndNotify(&addSubject, 32, 10);

    printf("\n");
    Detach(&addSubject, &dblObserver);
    // Раздельное изменение данных и явное обновление наблюдателей
    AddSubjectSet(&addSubject, 321, 101);
    NotifyAllObservers((Subject*)&addSubject);

    printf("\n");
    Subject.div divSubject;
    DivSubjectInit(&divSubject);
    Attach(&divSubject, &decimalObserver);
    // Attach(&divSubject, &hexObserver);
    VerifyAndAttach(&divSubject, &hexObserver);
    VerifyAndAttach(&divSubject, &dblObserver);
    // Изменение данных и обновление наблюдателей
    DivSubjectSetAndNotify(&divSubject, 32, 10);

    printf("\n");
    Detach(&divSubject, &hexObserver);
    // Раздельное изменение данных и явное обновление наблюдателей
    DivSubjectSet(&divSubject, 234, 567);
    NotifyAllObservers((Subject*)&divSubject);

    printf("\n");
    // Изменение данных и обновление наблюдателей
    DivSubjectSetAndNotify(&divSubject, 0, 0);

    printf("\n");
    // Изменение данных и обновление наблюдателей
    DivSubjectSetAndNotify(&divSubject, 10, 0);
  }

  int main() {
    ClientCode();
    return 0;
  }

Наблюдатель и процедурно-параметрическое программирование

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


Содержание