© 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);
}
Использование ПП подхода позволяет избавиться от основного недостатка Прототипа, заключающегося в необходимости реализовать операцию клонирования для каждого класса. Он обеспечивается использованием внешней функции, что позволяет реализовывать ее для любых уже созданных обобщений и их специализаций.
По всей видимости остаются проблемы, связанные с наличием во внутреннем представлении клонируемой переменной других связанных с ней переменной или при наличии круговых ссылок.
Содержание
|