V sobotu 2. listopadu proběhla mohutná oslava naší plnoletosti !!
Multimediaexpo.cz je již 18 let na českém internetu !!

Prototyp (návrhový vzor)

Z Multimediaexpo.cz

Verze z 17. 9. 2013, 19:59; Sysop (diskuse | příspěvky)
(rozdíl) ← Starší verze | zobrazit aktuální verzi (rozdíl) | Novější verze → (rozdíl)

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 take 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.[1]

Obsah

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 take 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 Prorotypů (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ání 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 take 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í:

  1. 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.
  2. Je možné klonovat pouze objekty, které implementují interface “Clonable”.
  3. Objekt, který není možné klonovat, vyhodí výjimku CloneNotSupported.

Výsledek klonování by měl splňovat následující podmínky:

  1. obj.clone() != x
  2. obj.clone().equals(obj)
  3. 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 i) {
        //To change body of created methods use File | Settings | File Templates.
        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()

Pravidla palce (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). Abstraktní továrna, Stavitel a Prototyp mohou použít při implementaci Jedináčka (Singleton) (GoF, p81, 134). Abstraktní továrna je často implmentována s Tovární metodou (vytváření přes dědičnost), ale mohou být take vytvořeny pomocí Prototypu (vytváření přes pověření). (GoF, p95)[2]

Často začnou programátoři používat Tovární metodu (jednodušší, vice 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 vice flexibility (GoF, p136).[2]

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).[2]

Z Prototypu může mít užitek i design, který hojně využívá vzory Strom a Dekorátor. (GoF, p126)[2]

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ěchny hodnoty s výchozími hodnotami. Např. pokud navrhujete system pro bankovní operace, budete chtít vytvořit kopi 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”.[2]

Reference

  1. James W. Cooper - The Design Patterns Java Companion, kniha o návrhových vzorech v jazyce Java
  2. 2,0 2,1 2,2 2,3 2,4 Prototype pattern, anglická verze článku na Wikipedii