A Chain of Responsibility egy Behavioral pattern, az objektumok viselkedési módját határozza meg.
Mikor alkalmazzuk: konkrét példával élve - egy adott inputot több szűrőn is végig kell vinni úgy, hogy a szűrőket könnyen ki és be lehessen építeni a szűrőrendszerbe.
Legyen az inputunk egy
class InputObject{ ... }
osztály egy példánya, amin többszintű szűrést szeretnénk végezni. Ekkor szükség van egy
/**
alaposztályra, amely nem tartalmaz logikát, csak definiálja a metódust, amelyet a konkrét implementációkban megvalósíthatjuk:
* @author 6aIL6ec
*
* A Filterek alaposztálya
*
*/
abstract class BaseFilter{
public abstract function handleInput($inputObject);
}
/**
* @author 6aIL6ec
*
* A BaseFilter egyik implementációja
*
*/
class Filter1 extends BaseFilter{
public function handleInput($inputObject){
[...]
}
}
/**
[...]
* @author 6aIL6ec
*
* A BaseFilter másik implementációja
*
*/
class Filter2 extends BaseFilter{
public function handleInput($inputObject){
[...]
}
/**
A célunk az, hogy ezeket egyenként "úszitsuk" rá az inputunkra, akár tetszőleges, akár előre meghatározott sorrendben.
* @author 6aIL6ec
*
* A BaseFilter N.k implementációja
*
*/
class FilterN extends BaseFilter{
public function handleInput($inputObject){
[...]
}
}
Több olyan ChOR implementációval is találkoztam, ahol a konkrét implementációk nem voltak kiemelve, hanem egymásba voltak ágyazva, így valóban láncot képeztek - ebben az esetben a Filter1 tartalmazná a Filter2-t, Filter2 a Filter3-t és így tovább.
Nem sok értelmét látom ennek a megoldásnak, mivel külön eljárás szükséges ahhoz, hogy a beágyazásokat kezeljen a rendszer - mi van akkor, ha az egyik szűrőt ki kell venni vagy más sorrendben kell elvégezni a szűrést?
Véleményem szerint egy külső, könnyen kezelhető hivatkozáslista merőben megkönnyíti a ChOR használatát.
Szükség van egy listára, amely tartalmazza a felhasználni kívánt Filter implementációkat, ebben az esetben a listát egy újabb osztály fogja tartalmazni:
/**
* @author 6aIL6ec
*
* A Filterek listáját tartalmazó osztály
*
*/
class FilterRegistry{
private static $filterArray = array(
"Filter1",
"Filter2",
"FilterN"
);
public function getFilterList(){
return self::$filterArray;
}
}
Végül kell egy olyan osztály is, amely elvégzi a példányosítást és a metódushívást:
/**
* @author 6aIL6ec
*
* A Filterek hívását végző osztály
*
*/
class FilterCaller{
public static function filterInputObject($inputObject){
$filterList = FilterRegistry::getFilterList();
foreach($filterList as $filter){
/*
* Factory Method design pattern-ről
* bővebben itt
*/
$concreteFilter = new $filter();
$concreteFilter->handleInput($inputObject);
}
}
}
Ennélfogva a
FilterCaller::filterInputObject($inputObject);
meghívása minden BaseFilter implementáción végigrángatja az inputunkat, méghozzá abban a sorrendben, amelyben a FilterRegistry-ben a Filtereket definiáltuk.