Prototyp (návrhový vzor)
Návrhový vzor Prototype patří do rodiny tzv. creational design patterns (vytvářecí návrhové vzory).
Používá se, když je vytváření instance třídy velmi časově náročné nebo nějak výrazně složité. Potom je výhodnější než náročně vytvářet mnoho instancí, vytvořit jednu a ostatní objekty z ní naklonovat. Může být také použit když jsou potřeba třídy, které se liší pouze v tom, jaké zpracování nabízejí.
Klonování se provádí implementací speciální metody, která vytvoří klon aktuálního objektu. Metoda může využívat systémové metody pro klonování (viz níže) nebo může objekt vytvářet jiným způsobem. Také může obsahovat další operace s objektem (změnu některých dat a vlastností objektu).
Obdobnou funkčnost může také poskytovat tovární metoda, která je jednodušší na implementaci.
Standardní výsledek klonování je tzv. mělká kopie (pokud je v objektu odkaz na další objekt, zkopíruje se pouze reference). Pro získání tzv. hluboké kopie (nekopírují se pouze reference, ale celé objekty) je možné buď takovou funkcionalitu integrovat od implementace metody clone objektu nebo se vytváří nová metoda deepClone.[zdroj?]
Význam použití návrhového vzoru Prototype
Použitím návrhového vzoru Prototype je možné přidávat a odebírat třídy za běhu klonováním, kdykoliv je potřeba. Je možné korigovat vnitřní data v závislosti na stavu programu. Je také možné specifikovat nové objekty, bez množení struktur tříd a dědičnosti. Třídy, které mají zacyklené reference na další objekty nemohou být klonovány. Využívá se i tzv. registr Prototypů (podobně jako registr Singletonů), kdy klonování obstarává registr a program se dotazuje registru. Jak bylo zmíněno, při klonování může být vyžadováno měnit některá data v závislosti na požadavcích programu. Pokud je taková potřeba očekávána, měla by třída implementovat také metody na práci s daty objektu – číst a přiřazovat hodnoty do atributů objektu.
Klonování
Výhodou klonování je, že program může pracovat s daty a různě je upravovat bez obav, že to ovlivní další části programu. To se může hodit např. při vracení interního pole (či kontejneru).
Implementace
Java
Klonování v jazyku Java je řešeno pomocí metody “clone”. Metoda vždy vrací objekt typu “Object”. Pro metodu “clone” existují tři významná omezení:
- Je to metoda s přístupem “protected”. To znamená, že může být zavolána pouze v rámci třídy nebo z modulu, který třídu obsahuje.
- Je možné klonovat pouze objekty, které implementují interface “Clonable”.
- Objekt, který není možné klonovat, vyhodí výjimku CloneNotSupported.
Výsledek klonování by měl splňovat následující podmínky:
- obj.clone() != obj
- obj.clone().equals(obj)
- obj.clone().getClass() == obj.getClass()
Potíž v implementaci návrhového vzoru Prototype v jazyce Java je, že pokud už třída existuje, nemusí být možné implementovat metody clone nebo deepClone. Speciálně metodu deepClone může být obtížné implementovat, pokud není možné deklarovat všechny objekty v třídě tak, aby implementovaly interface Serializable.
/**
* Prototype Class
*/
abstract class PrototypeFactory implements Cloneable {
public PrototypeFactory clone() throws CloneNotSupportedException {
// call Object.clone()
PrototypeFactory copy = (PrototypeFactory) super.clone();
//In an actual implementation of this pattern you might now change references to
//the expensive to produce parts from the copies that are held inside the prototype.
return copy;
}
abstract void prototypeFactory(int x);
abstract void printValue();
}
/**
* Concrete Prototypes to clone
*/
class PrototypeImpl extends PrototypeFactory {
int x;
public PrototypeImpl(int x) {
this.x = x;
}
void prototypeFactory(int x) {
this.x = x;
}
public void printValue() {
System.out.println("Value :" + x);
}
}
/**
* Client Class
*/
public class PrototypeExample {
private PrototypeFactory example; // Could have been a private Cloneable example.
public PrototypeExample(PrototypeFactory example) {
this.example = example;
}
public PrototypeFactory makeCopy() throws CloneNotSupportedException {
return this.example.clone();
}
public static void main(String args[]) {
try {
PrototypeFactory tempExample = null;
int num = 1000;
PrototypeFactory prot = new PrototypeImpl(1000);
PrototypeExample cm = new PrototypeExample(prot);
for (int i = 0; i < 10; i++) {
tempExample = cm.makeCopy();
tempExample.prototypeFactory(i * num);
tempExample.printValue();
}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
PHP
<?php
class SubObject
{
static $instances = 0;
public $instance;
public function __construct() {
$this->instance = ++self::$instances;
}
public function __clone() {
$this->instance = ++self::$instances;
}
}
class MyCloneable
{
public $object1;
public $object2;
function __clone()
{
// Force a copy of this->object, otherwise
// it will point to same object.
$this->object1 = clone $this->object1;
}
}
$obj = new MyCloneable();
$obj->object1 = new SubObject();
$obj->object2 = new SubObject();
$obj2 = clone $obj;
print("Original Object:\n");
print_r($obj);
print("Cloned Object:\n");
print_r($obj2);
?>
Python
import copy
#
# Prototype Class
#
class Cookie:
def __init__(self, name):
self.name = name
def clone(self):
return copy.deepcopy(self)
#
# Concrete Prototypes to clone
#
class CoconutCookie(Cookie):
def __init__(self):
Cookie.__init__(self, 'Coconut')
#
# Client Class
#
class CookieMachine:
def __init__(self, cookie):
self.cookie = cookie
def make_cookie(self):
return self.cookie.clone()
if __name__ == '__main__':
prot = CoconutCookie()
cm = CookieMachine(prot)
for i in xrange(10):
temp_cookie = cm.make_cookie()
Empirická pravidla (rules of thumb)
Někdy se návrhové vzory překrývají – jsou případy, kdy Prototyp ani Abstraktní továrna nebude vhodné použít. Jindy zase doplňují jeden druhý: Abstraktní továrna může ukládat sadu Prototypů, ze kterých se klonuje a vracejí se objekty (GoF, p126)[zdroj?]. Abstraktní továrna, Stavitel a Prototyp mohou použít při implementaci Jedináčka (Singleton) (GoF, p81, 134). Abstraktní továrna je často implementována s Tovární metodou (vytváření přes dědičnost), ale mohou být také vytvořeny pomocí Prototypu (vytváření přes pověření). (GoF, p95)[zdroj?]
Často začnou programátoři používat Tovární metodu (jednodušší, více přizpůsobitelná) a následně se vyvine v Abstraktní továrnu, Prototyp nebo Stavitele (pružnější, komplexnější) poté, co programátor zjistí, kde je potřeba více flexibility (GoF, p136)[zdroj?].
Rozdíl mezi Prototypem a Tovární metodou je, že Prototyp nevyžaduje dědění tříd, vyžaduje ale úvodní inicializaci. Naopak Tovární metoda vyžaduje dědění tříd, nevyžaduje inicializaci (GoF, p116)[zdroj?].
Z Prototypu může mít užitek i design, který hojně využívá vzory Strom a Dekorátor. (GoF, p126)[zdroj?]
Pokud budete potřebovat zavolat metodu clone() na objektu, když budete chtít vytvořit další objekt při běhu, který je skutečnou kopií objektu, který klonujete. Skutečná kopie znamená, že všechny atributy nově vytvořeného objektu by měly být stejné, jako u původního. Pokud by byla třída vytvořena pomocí klíčového slova “new”, měl by objekt všechny hodnoty s výchozími hodnotami. Např. pokud navrhujete systém pro bankovní operace, budete chtít vytvořit kopii objektu, který udržuje informace o vašem účtu, provést na něm požadované operace a potom jím přepsat původní objekt. V tomto případě budete chtít použít metodu clone() místo “new”.[zdroj?]
Reference
- GAMMA, Erich; HELM, Richard; JOHNSON, Ralph; VLISSIDES, John. Design Patterns: Elements of Reusable Object-Oriented Software. [s.l.]: Addison-Wesley, 1994. Dostupné online. ISBN 0-201-63361-2. (anglicky) , slangově GoF z Gang of Four