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

Top.Mail.Ru

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

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


Содержание


Прототип (Prototype)

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

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

Графическое представление ОО структуры Прототипа приведено на рисунке.

Структура паттерна Прототип

Структура паттерна Прототип

Объектно-ориентированная реализация Прототипа

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


  class Animal {
  protected:
    std::string name;
  public:
    Animal(std::string n): name{n} {}
    virtual ~Animal(){}
    virtual Animal *Clone() const = 0;
  };

В производных классах реализуется клонирование конкретных животных:


  class Duck : public Animal {
  public:
    Duck(std::string n): Animal(n) {
      std::cout << "The Duck " << name << " created\n";
    }
    Animal *Clone() const override {
      std::cout << "The clone of duck: ";
      return new Duck(name);
    }
  };

  class Dog : public Animal {
  public:
    Dog(std::string n): Animal(n) {
      std::cout << "The Dog " << name  << " created\n";
    }
    Animal *Clone() const override {
      std::cout << "The clone of dog: ";
      return new Dog(name);
    }
  };

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


  void Client() {
    std::cout << "Let's create a Duck\n";
    Duck duck("Grey Neck");
    std::cout << "\nLet's create a Clone of the Duck\n";
    Animal* duckClone = duck.Clone();
    delete duckClone;

    std::cout << "\nLet's create a Dog\n";
    Dog dog("Rex");
    std::cout << "\nLet's create a Clone of the Dog\n";
    Animal* dogClone = dog.Clone();
    delete dogClone;
  }

Процедурная схема Прототипа

Процедурная схема Прототипа вырождается в представление альтернатив, для которых создается только одна полиморфная функция(рисунок~\ref{ppp-prototype}). Все дополнительные функции являются сервисными довесками. Но их можно легко добавлять для решения различных задач.

Графическое представление ПП аналога Прототипа

Графическое представление ПП аналога Прототипа

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

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


  typedef struct Animal {const char* name;}<> Animal;
  void Init(Animal* a, const char* name) {a->name = name;}
  void GetInfo() = 0;
  Animal* Clone() {return NULL;}
  Animal* MemClone() {return NULL;}

Специализации и их обработчики формируются с использованием соответствующих основ, расширяющих обобщение:


  typedef struct Duck {} Duck;
  Animal + ;
  void GetInfo(){
    printf("The Duck %s created\n", a->name);
  }
  // Клонирование утки с созданием специализации
  Animal* Clone() {
    printf("The clone of duck: ");
    Animal* clone = create_spec(Animal.Duck);
    Init(clone, a->name);
    GetInfo();
    return clone;
  }
  // Клонирование утки с использованием копирования памяти
  Animal* MemClone() {
    printf("The memory clone of duck: ");
    unsigned long size = sizeof(*a);
    // Animal* clone = malloc(sizeof(Animal.Duck)); - пока не работает
    Animal* clone = (Animal*)malloc(size);
    memcpy(clone, a, size);
    GetInfo();
    return clone;
  }

  typedef struct Dog {} Dog;
  Animal + ;
  void GetInfo(){
    printf("The Dog %s created\n", a->name);
  }
  // Клонирование собаки с созданием специализации
  Animal* Clone() {
    printf("The clone of dog: ");
    Animal* clone = create_spec(Animal.Dog);
    Init(clone, a->name);
    GetInfo();
    return clone;
  }
  // Клонирование собаки с использованием копирования памяти
  Animal* MemClone() {
    printf("The clone of dog: ");
    unsigned long size = sizeof(*a);
    // Animal* clone = malloc(sizeof(Animal.Dog)); - пока не работает
    Animal* clone = (Animal*)malloc(size);
    memcpy(clone, a, size);
    GetInfo();
    return clone;
  }

Следует отметить, что в примере представлены по два варианта реализации клонирования. В первом случае специализация формируется специальной функцией \verb|create_spec| с последующей инициализацией клона. Во втором вариант осуществляется непосредственное копирование в клон области памяти из исходной переменной, используя специфику языка С, что позволяет полностью перенести все имеющиеся данные.

Клиентский код обеспечивает клонирование с применением любых функций:


  void Client() {
    printf("Let's create a Duck\n");
    struct Animal.Duck duck;
    Init((Animal*)&duck, "Grey Neck");
    GetInfo<(Animal*)&duck>();
    printf(
      "\nLet's create a Clone of the Duck "
      "using Clone function\n");
    Animal* duckClone = Clone<(Animal*)&duck>();
    free(duckClone);

    printf("\nLet's create a Dog\n");
    struct Animal.Dog dog;
    Init((Animal*)&dog, "Rex");
    GetInfo<(Animal*)&dog>();
    printf(
      "\nLet's create a Clone of the Dog "
      "using Clone function\n");
    Animal* dogClone = Clone<(Animal*)&dog>();
    free(dogClone);

    printf("\nLet's create a next Duck\n");
    struct Animal.Duck duck2;
    Init((Animal*)&duck2, "Drake");
    GetInfo<(Animal*)&duck2>();
    printf(
      "\nLet's create a Clone of the next Duck "
      "using MemClone function\n");
    duckClone = MemClone<(Animal*)&duck2>();
    free(duckClone);

    printf("\nLet's create a next Dog\n");
    struct Animal.Dog dog2;
    Init((Animal*)&dog2, "Gav");
    GetInfo<(Animal*)&dog2>();
    printf(
      "\nLet's create a Clone of the next Dog "
      "using MemClone function\n");
    dogClone = MemClone<(Animal*)&dog2>();
    free(dogClone);
  }

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

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


Содержание