Decorator szerkezeti minta

A sorozatunk folytatásában a szerkezeti mintákat fogjuk áttekinteni. Ezek közül is elsőként a decoratorral foglalkozunk. Röviden a decorator minta lényege, hogy futásközben tudjuk vele bővíteni a meglévő objektumunkat, úgy, hogy annak egységbezárása, funkcionalitása és kompatibilitása ne (vagy ha megengedőbbek vagyunk csak picit) sérüljön.

Felhasználhatóság és szempontok: 

  • Az objektum funkcionalitásának bővítése oly módon, hogy az más objektumokra ne legyen kihatással
  • A bővítések jellegük szerint nincsenek hatással az alap működésre, azok jellege kiegészítő tulajdonság vagy funkció.
  • Ha az örökléssel nem tudjuk vagy nem lenne praktikus megoldani. Általában túl sok a variációk száma és már csak ezért sem jöhet szóba az öröklés.

Rövid példával próbálom bemutatni a minta lényegét, és maradnék az előző html-s példáknál.
Ahogy fentebb írtam, a lényeg most a bővítésen van a hangsúly, szóval, van egy htmlInput objektumunk, ami a jól ismert <input type=’text’ … > elemet valósítja meg. Ezt szeretnénk kidekorálni egy <label> taggel, aminek ugye kapcsolata is van az <input> elemmek, mégpedig az ID attribútum. Hogy az input osztályunk is megmaradjon önállóan, de még is ki legyen bővítve, a következőképp segít a decorator minta.

A decorator minta PHP-s megvalósítása

<?php
// <input> megvalósítása
class htmlInput{
public $name = "";
public $value = "";
public $type = "text";
public $id = "";

public function __construct($id, $name, $value = ""){
$this->id = $id;
$this->name = $name;
$this->value = $value;
}

public function getOutput(){
return "<input id='{$this->id}' name='{$this->name}' value='{$this->value}' type='{$this->type}'/>";
}
}
// az input-t dekoráló osztály
class InputDecorator{
protected $htmlInput;
protected $title = '';

public function __construct(htmlInput $input, $title = ''){
$this->htmlInput = $input;
$this->title = $title;
}

public function setTitle($title){
$this->title = $title;
}

public function getOutput(){
$output = $this->htmlInput->getOutput();
return "<label for='{$this->htmlInput->id}'>{$this->title}</label>".$output;
}
}

$input_1 = new htmlInput('azon_0', 'input_neve', 'valami ertek');
echo "<br/>1:";//1:
echo htmlentities($input_1->getOutput());
//<input id='azon_0' name='input_neve' value='valami ertek' type='text'/>

echo "<br/>";
echo $input_1->getOutput();
$input_2 = new InputDecorator($input_1, 'cimke neve2');
echo "<br/><br/>2:";//2:
echo htmlentities($input_2->getOutput());
//<label for='azon_0'>cimke neve2</label><input id='azon_0' name='input_neve' value='valami ertek' type='text'/>

echo "<br/>";
echo $input_2->getOutput();
$input_3 = new InputDecorator($input_1, 'cimke neve3');
echo "<br/><br/>3:";//3:
echo htmlentities($input_3->getOutput());
//<label for='azon_0'>cimke neve3</label><input id='azon_0' name='input_neve' value='valami ertek' type='text'/>

echo "<br/>";
echo $input_3->getOutput();
?>

Ahogy látható az InputDecorator() konstruktorának átadjuk magát a htmlInput objektumot és az InputDecorator részévé tesszük tulajdonképpen, valamint bevezetünk új tulajdonságokat és függvényeket.
A megoldást lehet szigorítani abstract/interface bevezetéssel kedvünkre.

A javascript megengedőbb mivolta és a klasszikus öröklés hiánya miatt ettől azért picit eltér, viszont több lehetőség is van benne, lássuk a példát.

A decorator minta JavaScript példája:

// a htmlinput osztály
function HtmlInput(id, name, value){
this.type = 'text';
this.id = id;
this.name = name;
this.value = value;

this.getOutput = function(){
return "<input type='"+this.type+"' name='"+this.name+"' id='"+this.id+"' value='"+this.value+"' />";
}
}

// Label Decorator
function InputDecorator( Input, title ) {
var _output = Input.getOutput();
var _htmlInput = Object.create(Input);
_htmlInput.title = title;
_htmlInput.type = Input.type;
_htmlInput.id = Input.id;
_htmlInput.name = Input.name;
_htmlInput.value = Input.value;
_htmlInput.getOutput = function() {
return "<label for='"+_htmlInput.id+"'>"+_htmlInput.title+"</label>"+_output;
};
return _htmlInput;
}

var input_1 = new HtmlInput("azon_0", "email", "test(kukac)test.hu";);
console.log(input_1.getOutput());
//<input type='text' name='email' id='azon_0' value='test(kukac)test.hu'; /> var input_2 = InputDecorator(input_1, 'cimke');

console.log(input_2.getOutput());
//<label for='azon_0'>cimke</label><input type='text' name='email' id='azon_0' value='test(kukac)test.hu'; /> var input_3 = InputDecorator(input_1, 'cimke2');

console.log(input_3.getOutput());
//<label for='azon_0'>cimke2</label><input type='text' name='email' id='azon_0' value='test(kukac)test.hu'; />

Amint látjuk itt is létrejön egy új objektum, bár az is egy jó megoldás lenne, ha az InputDecorator szószerint a HtmlInput-t dekorálná ki és nem hozna létre új objektumot, viszont ez "sérti" azt az elvet, hogy az eredeti objektum maradjon meg, hiszen arra még szükség lehet.

Természetesen ne feledjük, hogy mindig több megoldás van, és mindig a projekttől függ, hogy melyiket kell alkalmazni. ;)

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.

Buy and Trade Bitcoin at Binance