Factory method pattern folosind PHP 5.3 si late static bindings

Read this post in English

Share on:

Factory method design pattern introdus de GoF (Gang of Four) pe care l-am abordat si eu intr-un blog anterior, are la baza ideea unei metode care genereaza obiecte. In general implementarea este destul de “slaba” pentru ca “factory”-ul este destul de hardcodat (la fel ca mine in blogul anterior).

PHP 5.3 ofera totusi posibilitatea unei implementari foarte interesante folosind “Late static bindings“.

Clasele pe baza carora se vor genera obiectele:

 1// clasa abstracta de baza care urmeaza sa fie mostenita
 2abstract class Drink {
 3
 4    // ingrediente
 5    protected $ingredients;
 6
 7    // metoda publica care genereaza bautura
 8    abstract public function MakeDrink();
 9}
10
11// o clasa derivata pentru ceai
12class Tea_Drink extends Drink {
13
14    // ingrediente pentru ceai
15    protected $ingredients = array('tea', 'sugar', 'mink', 'water');
16
17    // generare ceai
18    public function MakeDrink() {
19
20        // fa ceai
21    }
22}
23
24// clasa derivata pentru Bloody Mary
25class BloodyMary_Drink extends Drink {
26
27    // ingrediente Bloody Mary
28    protected $ingredients = array('votka', 'salt', 'tomato juice');
29
30    // generare Bloody Mary
31    public function MakeDrink() {
32
33        // fa BloodyMary
34
35    }
36}

Idea este de a avea o clasa abstracta care va fi extinsa intr-un mod cat mai simplu pentru a genera noi clase factory.

PHP 5

In PHP 5 clasa ar arata cam asa:

 1// clasa Factory abstracta
 2abstract class absFactory {
 3
 4    // numele clasei de baza
 5    static protected $base_class = '';
 6
 7    // metoda factory
 8    public static function getInstance($type) {
 9
10        // numele clasei rezultat
11        $class_name = $type . '_' . self::$base_class;
12
13        // se testeaza daca clasa exista
14        // aici se poate adauga si un autoloader eventual
15        if(!class_exists($class_name)) {
16            throw new Exception( 'Class ' . $class_name . ' not loaded!');
17        }
18
19        // se verifica daca clasa mosteneste clasa de baza
20        if(!is_subclass_of($class_name, self::$base_class)) {
21            throw new Exception(
22                'Class ' . $class_name . ' is not a child of ' . self::$base_class
23            );
24        }
25
26        // noul obiect
27        return new $class_name;
28
29    }
30
31}

Pentru ca metoda getInstance este statica si proprietatea este statica.

Daca incercam:

 1class DrinkFactory extends absFactory {
 2
 3    static protected $base_class = 'Drink';
 4}
 5
 6try {
 7
 8    $obj = DrinkFactory::getInstance('Tea');
 9
10} catch (Exception $e) {
11
12    echo $e->getMessage();
13}

Output-ul va fi:

1Class Tea_ not loaded!

Din pricina “self”, nu putem apela pur si simplu metoda getInstance() din clasa copil pentru ca valoarea lui $base_class va fi “” in loc de “Drink”, trebuie suprascrisa metoda getInstance(). Lucru prea “complicat”.

O versiune functionala in PHP 5 ar fi:

 1class DrinkFactory extends absFactory {
 2
 3    public static function getInstance($type) {
 4
 5        self::$base_class = 'Drink';
 6
 7        // metoda factory a clasei factory de baza
 8        parent::getInstance($type);
 9
10    }
11
12}
13
14try {
15
16    $obj = DrinkFactory::getInstance('Tea');
17
18} catch (Exception $e) {
19
20    echo $e->getMessage();
21}

Dar nu mi se pare tocmai “elegant”.

PHP 5.3

Aici avem “Late static bindings”, care in principiu este introducerea cuvantului “static”.

Clasa de baza factory arata cam asa:

 1// clasa Factory abstracta
 2abstract class absFactory {
 3
 4    // numele clasei de baza
 5    static protected $base_class = '';
 6
 7    // metoda factory
 8    public static function getInstance($type) {
 9
10        // numele clasei rezultat
11        $class_name = $type . '_' . static::$base_class;
12
13        // se testeaza daca clasa exista
14        // aici se poate adauga si un autoloader eventual
15        if(!class_exists($class_name)) {
16            throw new Exception( 'Class ' . $class_name . ' not loaded!');
17        }
18
19        // se verifica daca clasa mosteneste clasa de baza
20        if(!is_subclass_of($class_name, static::$base_class)) {
21            throw new Exception(
22                'Class ' . $class_name . ' is not a child of ' . static::$base_class
23            );
24        }
25
26        // noul obiect
27        return new $class_name;
28
29    }
30
31}

O schimbare atat de mica permite totusi o clasa factory mult mai “atragatoare”:

 1class DrinkFactory extends absFactory {
 2
 3     static protected $base_class = 'Drink';
 4
 5}
 6
 7try {
 8
 9    $obj = DrinkFactory::getInstance('Tea');
10
11} catch (Exception $e) {
12
13    echo $e->getMessage();
14}

Practic in aceasta varianta nu se inlocuieste decat proprietatea statica relevanta in acest context.