A Prototype tervezési minta
A prototype egy viszonylag gyakran használt tervezési minta, mely még mindig a létrehozási minták csoportjába tartozik. Leírás, PHP és Javascript példákkal a cikkben.
Most kifejezetten PHP-val kell kezdeni a prototype minta magyarázatát és megint a végére marad a Javascript. Szóval a prototype, akkor hasznos nekünk, ha menetközben kiderül, hogy egy objektumot le kellene másolni, mert abból kell nekünk még egy példány (tehát nem egy frissen inicializált dologból kell még egy).
Ez viszonylag egyszerűnek tűnik, de azért még sem az. Mivel itt is be kell tartani pár fontos dolgot például, hogy – mint ahogy azt már megszokhattuk – az objektum létrehozás módjának függetlennek kell lennie a rendszertől, valamint figyelni kell a sekély-mély másolás különbségre is (ez utóbbiról itt olvashattok bővebben). Ezért nem csak annyiból áll az új objektumlétrehozás, hogy simán lemásoljuk, mivel figyelni kell, hogy az objektumból (a prototípusa alapján) olyan új példány készüljön, ami teljesen önálló, tartalmazzon minden olyan friss adatot, amit kell, de ne tartalmazzon semmi olyan privát adatot, amiről az új objektumnak vagy annak felhasználójának "nem szabad tudnia". Valamint az új példánynak nem szabad kihatással lennie az eredetijére (ugye: shallow-deep copy).
De a prototype igazi lényeg az – ahogy már említettem -, hogy a létrehozandó objektumnak kell, hogy legyen egy prototípusa, ami gondoskodik a másolásról, annak menetéről, esetlegesen inicializál új vagy régi adattagokat. Tehát nem csak egy klónt kell teremteni, hanem a prototípusa alapján egy új objektumot, mivel menetközben az objektum is változik, így a prototípus biztosítja azt, hogy az új példány mindig megfeleljen a kritériumoknak.
A következő példa remélhetőleg érthetően vázolja a dolgot. Maradjunk a html elemeknél, adott egy input, ami a következőképp néz ki:
<input type="text" name="" value="" />
Ez ugye egy teljesen új, még csak nem is inicializált input, mivel a name tulajdonsága még kitöltetlen.
Ha ebből az inputból menetközben kell újat készíteni, akkor a következő igényeinket kell szem előtt tartani:
- a típusa maradjon az ami
- a name tulajdonságnak újnak kell lennie
- a value tulajdonságnak nem szabad átöröklődnie
A példa PHP-s megoldása:
<?php
// a htmlInput prototípusa
abstract class htmlInputPrototype{
protected $instance = 0;
protected $type = "text"; // az input típusa
public $name = ""; // az input nevét tárolja
public $value = ""; // az input értékét tárolja
abstract function __clone();
function __construct(){
;
}
function setName($name){ // név beállítása
$this->name = $name;
}
function setValue($value){ // érték beállítása
$this->value = $value;
}
function __toString(){ // az input elkészítése
return "<input type='".$this->type."' name='".$this->name."_".$this->instance."' value='".$this->value."'/>";
}
}
// az input osztálya
class htmlInput extends htmlInputPrototype{
function __construct(){
$this->instance = 1;
}
function __clone(){ // a klónozás szabályai
$this->type = "text";
$this->value = "";
$this->instance++;
}
}
$input_1 = new htmlInput('email'); // új példány
$input_1->setValue('teszt email cím';);
$input_1->setName('email');
echo "<br/>1:";
echo htmlentities($input_1);
$input_2 = clone $input_1;
$input_2->setValue('másik teszt email cím';);
echo "<br/>2:";
echo htmlentities($input_2);
var_dump($input_2);
?>
Amint látjuk az objektum elég primitív, sok mindent nem is tud, de a lényeg, hogy a PHP biztosítja számunkra az öröklődést és az absztrakt osztályt, sőt még a __clone() függvényt is. Ez nagyban segíti a prototype minta kialakítását, viszont picit el kell tekinteni a fent leírtaktól (majd a Javascriptnél már nem). Lényeg, hogy a prototípust az absztrakt osztály alkotja, ez biztosítja, hogy a klón megfeleljen az elvárásoknak, a __clone() függvény pedig (ezt kötelező minden gyermekobjektumnak saját magának definiálnia) a klónozásért felel, ebben megteremthetjük azokat a feltételeket, amelyek a klónra kell, hogy vonatkozzanak. A teszt részben csak egyszer kellett példányosítani, majd inicializálás után elég volt a klónozást használni.
Megemlítendő hátrány, hogy minden egyes prototípusnak implementálnia kell a clone() függvényt, ami igen bonyolult lehet.
A példa Javascriptes megoldása:
// a htmlinput prototípusa
function HtmlInputPrototype(proto){
this.proto = proto;
this.clone = function(value){ // klónozás a szabályoknak megfelelően
var htmlInput = Object.create(this.proto);
htmlInput.instance++;
htmlInput.value = value;
return htmlInput;
};
}
// a htmlinput osztály
function HtmlInput(name, value){
this.instance = 1;
this.type = 'text';
this.name = name;
this.value = value;
this.toString = function(){
return "<input type='"+this.type+"' name='"+this.name+"_"+this.instance+"' value='"+this.value+"' />";
};
}
var input_1 = new HtmlInput("email","teszt email cím";);
console.log(input_1.toString());
var input_2 = new HtmlInputPrototype(input_1).clone("másik teszt email cím";);
console.log(input_2.toString());
A Javascriptben nincs olyan öröklés és absztrakt osztály szerkezet, mint a PHP-ban ezért picit másképp közelítjük meg a dolgot. A létrejött input_1 objektumot a HtmlInputPrototype objektum fogja lemásolni a rávonatkozó szabályoknak megfelelően (this.clone() fv.). A lényegi részt az Object.create adja, viszont utána nekünk kell (a PHP-s példához hasonlóan) az adattagokat újrainicializálni, beállítani, vagy éppen törölni. Fontos megemlíteni, hogy a HtmlInputPrototype clone() metódusa bármely kompatibilis (HtmlInput) objektumot letudja klónozni.
Ne feledjük, ha prototype mintát használunk, akkor az objektum klónozáskor mindig használjunk mély másolást (deep-copy), hogy a klónozott objektum teljesen független legyen a prototípustól!
Ha tetszett a cikk, véleményed van, kiegészítenéd vagy hibát találtál, írj a cikk alatt egy kommentet, vagy írd meg emailben.