PHP и множественное наследование

[sid]

Новичок
PHP и множественное наследование

PHP не поддерживает множественное наследование, однако средства overloading, которые присутствуют в PHP5 позволяют реализовать ее некоторое подобие.
PHP:
class multipleInheritance
{
    private $classes = array();
    
    protected function __construct()
    {
        if ( func_num_args() < 2 )
        {
            throw new Exception("Invalid arguments count. Must be 2 or more");
        }
        $classes = func_get_args();
        foreach ( $classes as $class )
        {
            if ( !is_string($class) || strlen($class) <= 0 )
            {
                throw new Exception("Invalid class name");
            }elseif ( !class_exists($class) )
            {
                throw new Exception("Class '{$class}' did not exsit");
            }elseif ( isset($this->classes[$class]) )
            {
                throw new Exception("Class '{$class}' must be refered only once in class declaration");
            }
            $this->classes[$class] = new $class;
        }
    }
    
    public function __get($key)
    {
        $objects = $this->getObjectsHaveProperty($key);
        $objectsCnt = count($objects);
        if ( $objectsCnt <= 0 )
        {
            throw new Exception("Invalid property '{$key}'.");
        }elseif ( $objectsCnt > 1 )
        {
            throw new Exception("Property '{$key}' have more than one class.");
        }
        return $objects[0]->$key;
    }
    
    public function __set($key, $value)
    {
        $objects = $this->getObjectsHaveProperty($key);
        $objectsCnt = count($objects);
        if ( $objectsCnt <= 0 )
        {
            return $this->$key = $value;
        }elseif ( $objectsCnt > 1 )
        {
            throw new Exception("Property '{$key}' have more than one class.");
        }
        return $objects[0]->$key = $value;
    }
    
    // Прокси метод, редиректор... называйте как хотите :)
    public function __call($string, $args)
    {
        $objects = $this->getObjectsImplementsMethod($string);
        $objectsCnt = count($objects);
        if ( $objectsCnt <= 0 )
        {
            throw new Exception("Method '{$string}' not implemented");
        }elseif ( $objectsCnt > 1 )
        {
            $classNames = array();
            foreach ( $objects as $object )
            {
                $classNames[] = get_class($object);
            }
            $object = implode(', ', $classNames);
            throw new Exception("Method '{$string}' implemented by classes {$object}. You must overload this method in child class");
        }else{
            $callAddress = array($objects[0], $string);
            $arguments = $args;
            return call_user_func_array($callAddress, $arguments);
        }
    }
    
    // Возвращает массив объектов, которые реализуют метод $methodName
    protected function getObjectsImplementsMethod($methodName)
    {
        $ret = array();
        foreach ( $this->classes as $class )
        {
            if ( method_exists($class, $methodName) )
            {
                $ret[] = $class;
            }
        }
        return $ret;
    }
    
    // Возвращает массив объектов, которые обладают свойством $propName
    protected function getObjectsHaveProperty($propName)
    {
        $ret = array();
        foreach ( $this->classes as $class )
        {
            $properties = get_object_vars($class);
            if ( in_array($propName, array_keys($properties)) )
            {
                $ret[] = $class;
            }
        }
        return $ret;
    }
}
Уверен, что знающие люди уже поняли суть работы.

Как можно использовать подобный класс для реализации множественного наследования?

PHP:
class A
{
    public $a = 'foo A';
    
    public function testA()
    {
        return 'Hi this is '.__METHOD__;
    }
    public function foo()
    {
        return 0;
    }
}

class B
{
    public $b = 'foo B';
    
    public function testB()
    {
        return 'Hi this is '.__METHOD__;
    }
    
    public function foo()
    {
        return 0;
    }
}

// Этот класс наследуется от классов A и B
class foo extends multipleInheritance
{
    public $u = 'asd';
    function __construct()
    {
        multipleInheritance::__construct('A', 'B');
    }
}
Здесь класс foo наследуется от классов A и B.

Давайте проверим.

PHP:
try
{
    $object = new foo();
    $ret = $object->testA();            // Вызывается A::testA()
    echo "A::testA returned '{$ret}'\n";
    $ret = $object->testB();            // Вызывается B::testB()
    echo "B::testB returned '{$ret}'\n";
    echo "A::a is '{$object->a}'\n";    // Выводится A::a
    echo "B::b is '{$object->b}'\n";    // Выводится B::b
    echo "A::a is '".($object->a = 'bar A')."'\n";    // Меняется A::a
    echo "B::b is '".($object->b = 'bar B')."'\n";    // Меняется B::b
    $object->foo();         // Генерируется ошибка, так как метод foo
                            // определен в двух классах
} catch ( Exception $e )
{
    echo "Sorry, failed because of ".$e->getMessage();
}
Вобщем получается следующая картина.

Плюсы:
  • Можно прозрачно вызывать методы двух родительских классов
  • Можно получать доступ к свойствам родительских классов
  • Можно использовать оператор :: для вызова метода конкретного родительского класса
А теперь минусы:
  • Использования оператора :: может привести к непредсказуемым последствиям, так как методу передается контекст дочернего объекта который фактически наследуется от multipleInheritance, а не от A и B
  • При использовании оператора -> метод вызываеться в контексте своего класса (родительского, а не дочернего), со всеми вытекающими. То есть вы не сможете вызвать из родительского класса метод дочернего

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

lucas

Guest
Конкретный вопрос: когда может быть нужно множественное наследование (уточним: в таком "оригинальном" варианте, применительно к web-программированию)?
 

[sid]

Новичок
filter
Наследование от интерфейсов это наследование как раз интерфейса, а не реализации. Наследование интерфейса не позволяет наследовать реализацию. Плюс интерфейсы в PHP не могут быть наследованы от нескольких классов (как в Java).

lucas
Действительно, это редкий случай. Однако иногда множественное наследование может предотвратить комбинторный рост количества классов, когда вы пытаетесь описать сущность с точки зрения категорий. Например я являюсь сыном своих родителей, студентом и гражданином РФ, в то время как другой человек не является студентом, но подходит по две другие категории! В отсутствие множественного наследования вам пришлось бы либо использовать интерфейсы и дублировать код или вводить промежуточные классы, что тоже не есть гуд!
 

crocodile2u

http://vbolshov.org.ru
Может пригодиться. Правда, все методы и переменные классов А и В (это - применительно к приведенному примеру), которые будет использовать класс foo, должны быть public :(
 

lucas

Guest
[sid]

В любом случае можно поступить более "ортодоксально", например, реализовав в каждом потомке класса Person механизм регистрации свойств объекта "студент", "гражданин" и т. д. (аггрегация/ассоциация).

Хотя, соглашусь с crocodile2u: полезно это может быть, несмотря на сложности с совместимостью с объектной моделью PHP.
 

[sid]

Новичок
filter
А по моему, для PHP5. Функции Aggregate доступны только в "четверке". В PHP5 они и функции ClassKit заменены расширением RunKit, который я не смог запустить на 5.1. Apache умирал при чтении памяти по такому-то адресу (наверное еще сыроват). В 5,0 не проверял!
 

_RVK_

Новичок
Просто пример когда бы мне хотелось применить множественноле наследование.
есть класс base_view_model, которая имеет базовые методы для получения выборок из БД(абстрактный). Есть класс base_edit_model(абстрактный) которая содержит методы для изменения данных в БД. я хочу иметь набор классов таких как edit_news, view_news, view_and_edit_news. т.е в одном случае мне нужен класс, которй может только редактировать новости, в другом класс, который только отображает новости, в третьем, умеет выполнять и то и другое.
 

[sid]

Новичок
Конечно IMHO! Но помоему тут лучше применить композицию/агрегацию!
 

_RVK_

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

tony2001

TeaM PHPClub
Просто пример когда бы мне хотелось применить множественноле наследование.
есть класс base_view_model, которая имеет базовые методы для получения выборок из БД(абстрактный). Есть класс base_edit_model(абстрактный) которая содержит методы для изменения данных в БД. я хочу иметь набор классов таких как edit_news, view_news, view_and_edit_news. т.е в одном случае мне нужен класс, которй может только редактировать новости, в другом класс, который только отображает новости, в третьем, умеет выполнять и то и другое.
множественное наследование тут нужно как ослу пятая нога.
 

[sid]

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

autosoft

Guest
К ограничениям данного подхода можно отнести ещё один (не описанный в статье журнала PHP Inside №14). Помимо необходимости объявлять свойства и методы как public так же нельзя использовать ссылочные параметры в аргументах методов класса.

Это можно пояснить примером:

PHP:
<?php

require_once("./multipleInheritance.class.php");

class a {

    public $a;

    function set_v(&$v) { $v = $this->a; }
}

class b {

    public $b;
}

class c extends multipleInheritance {

    public function __construct() {
        multipleInheritance::__construct("a", "b");
    }
}

$c = new c();

$c->a = 100;
$c->b = 200;

$v = 10;

$c->set_v($v);

echo $c->a, ", ", $c->b, ", ", $v;

// Результат: 100, 200, 10

// Ожидалось: 100, 200, 100

?>
Но в целом идея очень хорошая и значительно «расширяет» возможности PHP в объектно-ориентированном программировании.
 

Igor aka TiGR

Новичок
Мне лично потребовалось множественное наследование в следующей ситуации: Есть модуль большой системы (модулей полно), называется file для обработки загрузок файлов. Есть его потомок, advancedfile, который позволяет загружать файлы не только со своего компьютера, но также и из интернета и с локальной системы (конечно, при наличии соотв. прав) и обрабатывать это дело, обеспечивая безопасность.

Есть также модуль image. Он является потомком file, и содержит возможности проверки загружаемого файла изображения на соответствие параметрам (ширина, высота, целостность, реальный тип графического файла), а также при необходимости масштабировать изображение.

Есть желание сделать image, являющийся потомком advancedfile. Как это сделать?

Простейший способ - выделить все относящиеся к обработке и проверке изображений методы в отдельный класс, например image_core, a потом использовать множественное наследование:

class image extends file, image_core

и

class advancedimage extends advancedfile, image_core

Другого пути я не увидел (кроме дублирования кода, что является неприемлемым вариантом). Может быть кто подскажет?

Поэтому, я с живым интересом отнёсся к этой статье, и думаю попробовать применить (если не найду других вариантов).
 

vitus

мимо проходил
// Этот класс наследуется от классов A и B
class foo extends multipleInheritance
{
public $u = 'asd';
function __construct()
{
multipleInheritance::__construct('A', 'B');
}
}
- ниразу не наследуется, проверь:

$object = new foo();

$object instanceOf A
$object instanceOf B

Igor aka TiGR
сделай абстрактный класс InputStream, для загрузок

class FileInputStream extends InputStream{...}
class HTTPInputStream extends InputStream{...}
итд

сделай
class Image{
...
public function load(InputStream in){...}
...
}

- наследование - не единственный способ повторного использования кода
 

alex_kh

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

Как можно обойтись интерфейсами лучше всего показано в библиотеке VCL
 

robocomp

Новичок
http://www.tml.tkk.fi/~pnr/Tik-76.278/gof/html/Decorator.html

по-моему, зачастую вот этот шаблон помогает избежать множественного наследования.
Например, как раз с примером про "студента, сына и прочих уродцев"

Правда текстового описани я пока не нашёл, кроме вот этого
http://www.phppatterns.com/docs/design/decorator_pattern?s=decorator
 
Сверху