Блог


Паттерны в PHP (Factory, Abstract Factory)

Эти два паттерна настолько родственны, что их не стоит даже разносить по разным темам. Попробую объяснить в одной, постепенно. Тем более это как раз самая удобная форма объяснений.

И сразу отмечу некоторый парадокс. Фабрика, это один из наиболее узнаваемых паттернов, однако в PHP грамотно применить его довольно сложно, так как он полезен тогда, когда нужно сделать много однотипных объектов. Это полезно в десктопных приложениях, допустим играх. Где нужна целая армия эльфов, гномов и прочей нечисти. Они живут долго, у них разное оружие, разное "здоровье", некоторые из них могут героически погибнуть в конце концов. Тогда с помощью фабрики можно наштамповать новых.

В PHP объекты долго не живут. Да и много однотипных объектов при генерации страницы бывает нужно крайне редко. Так что обычно ничего, кроме усложнения системы, фабрики не привносят. Но изучить их нужно, хотя бы для того, чтобы понять, где можно избежать лишней сложности.


Итак, фабрика.

Представьте себе, что перед нами стоит задача сделать... фабрику. Фабрику игрушек. Каждая игрушка, это самостоятельный объект.


Сначала сделаем саму игрушку. Допустим это машинка. Она должна уметь ездить.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 

class Car
{
    public function 
drive()
    {
        return 
'Поехали!';
    }
}

$car = new Car;
echo 
$car->drive();


 


А еще будет лодка. Она должна уметь плавать.

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php 

class Boat
{
    public function 
drive()
    {
        return 
'Поплыли!';
    }
}

$boat = new Boat;
echo 
$boat->drive();


 


Теперь нужно построить саму фабрику, которая будет выпускать игрушки в неограниченом количестве. Всё лучшее - детям!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php   
// Машинка 
class Car  
{  
    public function 
drive()  
    {  
        return 
'Поехали!';  
    }  
}  
// Лодка 
class Boat 

    public function 
drive() 
    { 
        return 
'Поплыли!'
    } 

// А вот и фабрика 
class Factory 
{   // Производим машинки
    
public function createToyCar() 
    { 
        return new 
Car
    } 
    
// Производим лодки
    
public function createToyBoat() 
    { 
        return new 
Boat
    } 


// Теперь производством игрушек занимается фабрика 

$factory = new Factory
$car  $factory->createToyCar(); 
$boat $factory->createToyBoat(); 
echo 
$car->drive(); 
echo 
$boat->drive(); 


 


Схематично это выглядит так:




Это самая простая реализация паттерна Factory. Фабрика в таком виде, в PHP, как я говорил, полезна в очень редких случаях. Только если объекты настраиваются внутри фабричных методов. Но это уже больше похоже на паттерн builder (билдер). Еще они полезны, когда нужно создавать объекты динамически, из строки. Как в этом примере:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

class Factory
{
    public function 
createToy($name)
    {
        if (
class_exists($name)) {
            return new 
$name;
        } 
        
        throw new \
Exception('Такие игрушк фабрика не производит.');
    }
}

// Теперь производством игрушек занимается фабрика

$factory = new Factory;
$car  $factory->createToy('Car');
$boat $factory->createToy('Boat');
echo 
$car->drive();
echo 
$boat->drive();


 



Это уже больше похоже на паттерн "фабричный метод", его рассмотрим попозже.


Ну а теперь разберем ситуацию посложнее. Допустим нам нужно расширить ассортимент за счет использования разных материалов. Половина игрушек будут деревянными, половина пластмассовыми. Тогда нам потребуются уже конкретные фабрики машинок и лодок. Одна выпускает пластиковые и деревянные машинки, другая, соответственно, пластиковые и деревянные лодки.

А объединяет это все хозяйство общий абстрактный класс. Абстрактная фабрика. Она потому абстрактная, что понятия не имеет, что изготавливают цеха (конкретные фабрики). Их, кстати, может быть и не две, можно еще кукол выпускать или пистолетики. Она отвечает за то, что бы игрушки изготавливались только из имеющихся материалов. 


Вот так можно отобразить на схеме (пунктир - создание объекта, жирные линии - наследование):

 




И в кодовом выражении:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?php     

// Абстрактная фабрика  
abstract class AbstractFactory    
{    
    abstract public function 
createFromPlastic();    
    abstract public function 
createFromWooden();    
}  

// Фабрика машинок   
class CarFactory  extends AbstractFactory  
{   // Производим пластмассовые машинки   
    
public function createFromPlastic()    
    {    
        return new 
PlasticCar;    
    }    
    
// Производим деревянные машинки   
    
public function createFromWooden()    
    {    
        return new 
WoodenCar;    
    }    
}   

// Фабрика лодок  
class BoatFactory  extends AbstractFactory  
{   // Производим пластмассовые лодки    
    
public function createFromPlastic()    
    {    
        return new 
PlasticBoat;    
    }    
    
// Производим деревянные лодки   
    
public function createFromWooden()    
    {    
        return new 
WoodenBoat;    
    }    
}  
   
// Пластиковая машинка    
class PlasticCar     
{  
    protected 
$material 'Пластмасса - ';   
      
    public function 
drive()     
    {     
        return 
$this->material .'Поехали!';     
    }     
}   
  
// Деревянная машинка   
class WoodenCar    
{   
    protected 
$material 'Дерево - ';  
      
    public function 
drive()     
    {     
        return 
$this->material .'Поехали!';     
    }   
}   

// Пластиковая лодка    
class PlasticBoat   
{  
    protected 
$material 'Пластмасса - ';   
      
    public function 
drive()     
    {     
        return 
$this->material .'Поплыли!';     
    }     
}

// Деревянная лодка   
class WoodenBoat   
{   
    protected 
$material 'Дерево - ';  
      
    public function 
drive()     
    {     
        return 
$this->material .'Поплыли!';     
    }   
}  
 
//////////////////////////////////////////////////    

    
$toy 'Car'

    switch (
$toy
    { 
        case 
'Car' 
            
$factory = new CarFactory;          
        break; 
         
        case 
'Boat' 
            
$factory = new BoatFactory;          
        break; 
    } 

    
$car $factory->createFromPlastic();       
    echo 
$car->drive();


 



Ну а дальше дело техники - вынести повторяющийся функционал в отдельные классы и  т.д. Главное теперь должно быть понятно. Фабрика - для инициализации объектов "на потоке". Абстрактная фабрика - ограничитель для конкретных фабрик семейства.

Теги:

Комментарии (0)


 
Наверх