Прототип (шаблон проєктування)
Прототип (англ. Prototype) — твірний шаблон проєктування, який дозволяє створювати копії існуючих об'єктів таким чином, що програмний код не залежить від їх класів.
Цей шаблон застосовують у випадку, коли тип об'єктів, що створюються заданий екземпляром прототипу, що використовується для створення нових об'єктів шляхом копіювання цього прототипу. Він використовується для:
- уникнення успадкування класу, що створює ці об'єкти в клієнтському застосуванні, як це відбувається при використанні фабричного методу.
- уникнення витрат, що виникають при створенні нових об'єктів стандартним способом (тобто при використанні ключового слова 'new'), коли це може бути занадто марнотратно для даного застосування.
Для реалізації цього шаблону, необхідно визначити абстрактний базовий клас (інтерфейс), який визначає чисто віртуальний метод clone(). Будь-який клас, що потребує виконання функціоналу «поліморфного конструктора» буде успадковувати цей базовий абстрактний клас, і реалізовувати операцію clone().
Клієнт, замість того, щоб писати код, який би використовував оператор «new» із жорстко вказаним іменем класу, викликає метод clone() для прототипу.
Прикладом ситуації, що демонструє шаблон Прототип є мітотичний поділ живих клітин — що призводить до створення двох ідентичних клітин — тут прототип відіграє важливу роль в створенні повної копії самого себе. Коли відбувається ділення клітини, в результаті утворюються дві ідентичні клітини із ідентичним генотипом. Іншими словами, клітина клонує сама себе.[1]
Опис
Шаблон проектування Прототип [2] є одним із двадцяти трьох добре відомих шаблонів, із книги Банди чотирьох (GoF) який описує як вирішити рекурсивні задачі проектування, для створення гнучкого і придатного для повторного використання об'єктно-орієнтованого програмного забезпечення, у такий спосіб, щоб об'єкти було легше реалізовувати, змінювати, тестувати, і повторно використовувати.
Призначення
Задає види об'єктів, що створюються, за допомогою екземпляру-прототипу, та створює нові об'єкти шляхом копіювання цього прототипу.
Шаблон проектування Прототип дозволяє вирішити такі проблеми як: [3]
- Як об'єкти можуть створюватися у такий спосіб?, що те, який об'єкт буде створено може бути визначено під час виконання коду?
- Як можна створити екземпляри динамічно завантажених об'єктів?
Створення нових об'єктів напряму за допомогою класу, що потребує (використовує) певні об'єкти є не достатньо гнучким, оскільки змушує прив'язати клас до певних об'єктів під час компіляції, і стає неможливо визначити які об'єкти будуть створені під час виконання коду.
Шаблон проектування прототип визначає спосіб, як вирішити цю задачу:
- Визначити об'єкт
Prototype
(прототип), що створює копію самого себе. - Створити новий об'єкт шляхом копіювання об'єкту
Prototype
.
Це дозволяє створювати конфігурації класу із різними об'єктами прототипу Prototype
, які будуть копіюватися для створення нових об'єктів, і навіть більше, об'єкти Prototype
можуть додаватися і знищуватися під час виконання.
Застосування
Слід використовувати шаблон Прототип коли:
- класи, що інстанціюються, визначаються під час виконання, наприклад за допомогою динамічного завантаження;
- треба запобігти побудові ієрархій класів або фабрик, паралельних ієрархій класів продуктів;
- екземпляри класу можуть знаходитись у одному з не дуже великої кількості станів. Може статися, що зручніше встановити відповідну кількість прототипів та клонувати їх, а не інстанціювати щоразу клас вручну в слушному стані.
Емпіричні правила
Іноді твірні шаблони збігаються один з одним — існують випадки, коли більш доречно використовувати або прототип або абстрактну фабрику. А в інших випадках вони доповнюють один одного: абстрактна фабрика може зберігати множину прототипів, з яких буде створюватися копія і повертатимуться утворені об'єкти (GoF, с. 126). Абстрактна фабрика, будівник, і прототип можуть в своїй реалізації використовувати шаблон Одинак. (GoF, сc. 81, 134). Класи абстрактних фабрик часто реалізовані за допомогою фабричних методів (створення через успадкування), але вони можуть також реалізовуватися за допомогою прототипу (створення шляхом делегування). (GoF, с.95)
Часто, проектування починається із використання фабричного методу (менш складно, піддається кращому налаштовуванню, поширюється на підкласи) і еволюціонує в сторону абстрактної фабрики, прототипу, або будівника (більш гнучкі і більш складні) якщо архітектор коду виявляє де потребується більша гнучкість поведінки. (GoF, с.136)
Прототип не потребує створення підкласів, але потребує процедури «ініціалізації». Фабричний метод потребує створення підкласів, але не потребу ініціалізації. (GoF, с. 116)
Архітектури, які широко використовують шаблони компонувальник та декоратор, також можуть отримати вигоду із використання прототипу. (GoF, с.126)
Емпіричне правило може полягати в тому, що вам може знадобитися метод копіювання clone() Об'єкту коли ви хочете створити інший об'єкт під час виконання коду, який є повною копією того об'єкта, що ви копіюєте. Повна копія означає, що всі атрибути новоствореного об'єкту будуть такими ж самими, як і в того об'єкта який ви клонуєте. Якби замість цього ви створювали екземпляр даного класу використовуючи ключове слово new, ви б отримали об'єкт із атрибутами, що мають початкові не задані значення.
Наприклад, якщо ви використовуєте систему, що здійснює транзакції із банківським рахунком, тобі б вам знадобилося створити копію об'єкту, що зберігає в собі інформацію про цей рахунок, здійснити над ним транзакцію, а потім замінити оригінальний об'єкт на змінений. В даному випадку ви б захотіли використати метод clone() замість new.
Структура
- Prototype — прототип:
- визначає інтерфейс для клонування самого себе;
- ConcretePrototype — конкретний прототип:
- реалізує операцію клонування самого себе;
- Client — клієнт:
- створює новий об'єкт, звертаючись до прототипу із запитом клонувати себе.
Переваги
- Створює глибоку копію комплексної ієрархії об'єктів
- Зменшення навантаження ініціалізації
- Багаторазові можливості — оптимізовані зусилля щодо кодування
- Спрощений процес копіювання об'єктів:
Недоліки
- Додаткові витрати на розробку
- Потребує уважності і точності при глибокому копіюванні
Відносини
Клієнт звертається до прототипу, щоб той створив свого клона.
Реалізація
Приклад С++
#include <iostream>
#include <string>
#include <vector>
using namespace std;
struct Prototype
{
virtual Prototype* Clone() = 0;
};
class Item : public Prototype
{
public:
int ID;
string Name;
public:
Item(string strName) : Name(strName)
{
ID = GetNewID();
};
// конструктор копіювання
Item(Item& item) : Name(item.Name)
{
ID = GetNewID();
};
virtual ~Item() { };
virtual Prototype* Clone()
{
return new Item(*this);
};
static int GetNewID()
{
static int ID = 0;
return ++ID;
};
};
void print(Prototype* p)
{
Item* pItem = dynamic_cast <Item*> (p);
cout << "ID: " << pItem->ID << endl;
cout << "Name: " << pItem->Name << endl;
cout << endl;
}
void main()
{
vector<Prototype*> items;
Item Item("Concrete Item");
// клонуємо об'єкт
for (int i = 0; i < 10; ++i)
{
items.push_back
(
Item.Clone() // виклик методу клонування
);
}
// Друкуємо клонів
for (int i = 0; i < 10; ++i)
{
print(items[i]);
}
}
Приклад С#
using System;
using System.IO;
using System.Drawing;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
namespace PrototypePattern
{
static class Prototype
{
public static T DeepCopy<T>(this T self)
{
var formatter = new BinaryFormatter();
using (var memoryStream = new MemoryStream())
{
formatter.Serialize(memoryStream, self);
memoryStream.Seek(0, SeekOrigin.Begin);
var clone = formatter.Deserialize(memoryStream);
return (T)clone;
}
}
}
[Serializable]
class TreePrefab
{
private int _height;
private Color _color;
public TreePrefab(int height, Color color)
{
_height = height;
_color = color;
}
public void SetColor(Color color)
{
_color = color;
}
}
class Program
{
static void Main(string[] args)
{
var treePrefab = new TreePrefab(2, Color.Green);
var forest = new List<TreePrefab>();
for (int i = 0; i < 10; i++)
{
var tree = treePrefab.DeepCopy();
tree.SetColor(i % 2 == 0 ? Color.Green : Color.YellowGreen);
forest.Add(tree);
}
}
}
}
Примітки
- Duell, Michael (July 1997). Non-Software Examples of Design Patterns. Object Magazine 7 (5): 54. ISSN 1055-3614.
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. с. 117ff. ISBN 0-201-63361-2.
- The Prototype design pattern - Problem, Solution, and Applicability. w3sDesign.com. Процитовано 17 серпня 2017.
Література
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.