Блог


Наследование или делегирование.

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

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

Наверняка вы знаете, что механизм наследования включается директивой extends. Однако мало кто задумывался над тем, почему так. Почему назвали extends (расширение), а не скажем inheritage (наследование как есть). Все дело в том, что наследование - термин притянутый за уши, и мало имеет отношения к этому механизму.

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

Другое дело, если перевести правильно - расширение. И тогда нам нужно будет собрать общие признаки в один базовый класс (скажем "животные"), а его уже расширять возможностями кошки или собаки. Потому что нельзя унаследовать общие признаки от абстрактного понятия. Нельзя сказать "я наследник homo sapiens". Наследником можно быть конкретных папы, мамы, дедушки, бабушки. Homo sapiens можно быть только представителем, расширяя базовые понятия собственной индивидуальностью.

Но это терминология, особой сути не меняет. Если правильно понимать суть механизма, то нет разницы, как назвать, Однако при таком подходе закрадывается опасность повторения кода, что сводит на нет достижения ООП.

Рассмотрим пример. У нас есть базовый класс "животные". И нам нужно сделать собаку, кошку и кота. Общие признаки мы можем унаследовать, однако собака не может лазить по деревьям, а кот и кошка могут. А значит в реализации классов tomcat и pussycat нам придется продублировать этот функционал:

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
<?php 

class animals
{
    protected 
$fur;
    protected 
$tail;    
    protected 
$paw 4;
}

class 
dog extends animals
{
    public function 
growl(){}
}

class 
pussycat extends animals
{
    public function 
climbTree()
    {
    
// Тут код лазанья по деревьям
    
}
}

class 
tomcat extends animals
{
    protected 
$scrotum true;
    
    public function 
climbTree()
    {
    
// Повтор кода
    
}
}



Однако! Возразите вы. Можно сделать промежуточный класс cats, реализовать этот функционал в нем и наследоваться уже от кошек. Тогда повтора не будет. 

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
<?php 

class animals
{
    protected 
$fur;
    protected 
$tail;    
    protected 
$paw 4;
}

class 
cats extends animals
{
    public function 
climbTree()
    {
    
// Тут код лазанья по деревьям
    
}
}

class 
pussycat extends cats
{

}

class 
tomcat extends cats
{
    protected 
$scrotum true;
}



Да, это так. Однако это влечет за собой усложнение иерархии и не спасает от повторов при глобальном рассмотрении. Допустим у нас есть не собака, а мишка-коала. Он тоже может лазить по деревьям, но это не кошка. Так что волей-неволей придется дублировать код в его ветке.

И вот тут полезно сделать делегирование. Создать отдельный класс, который будет выполнять определенные задания. Допустим назвать "действия". И реализовать в нем функционал лазанья по деревьям. Тогда будет такая картина:

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
<?php 
// Базовый класс
class animals
{
    protected 
$fur;
    protected 
$tail;    
    protected 
$paw 4;
}
// Хэлпер 
class actions
{
    public function 
climbTree()
    {
    
// Тут код лазанья по деревьям
    
}
}

class 
koala extends animals
{   // И мишка может лазить по деревьям
    
public function climbTree()
    {
        
$obj = new actions();
        
$obj->climbTree();
    }
}

class 
pussycat extends animals
{
    
// И кошка.
    
public function climbTree()
    {
        
$obj = new actions();
        
$obj->climbTree();
    }
}



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

Зато это очень красиво и полезно в коде.

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

Да и прибудет с Вами сила!

 

Николай aka twin

Теги: Паттерны | PHP

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

Слава
14-06-2015
Похоже что ООП, это способ упрощения не самого програмного кода, а собственно проетирования приложения. Долгое время не мог вообще понять суть ООП, пока не осмыслил, что такое екземпляр класса, а осмыслил сразу, как только решил вывести сам екзмпляр-то на екран.

 
Наверх