Могу ли я расширить класс, используя более 1 класса в PHP?


150

Если у меня есть несколько классов с функциями, которые мне нужны, но я хочу хранить их отдельно для организации, могу ли я расширить класс, чтобы иметь оба?

т.е. class a extends b extends c

редактировать: я знаю, как расширять классы по одному, но я ищу способ мгновенного расширения класса, используя несколько базовых классов - AFAIK, вы не можете сделать это в PHP, но должны быть способы обойти это, не прибегая к class c extends b,class b extends a


Используйте агрегацию или интерфейсы. Множественное наследование не существует в PHP.
Франк

2
Я смотрю на интерфейсы, поскольку я не большой поклонник больших иерархий классов. Но я не вижу, как интерфейсы на самом деле делают что-нибудь?
atomicharri

Интерфейсы позволяют вам «наследовать» только API, а не тела функций. Это заставляет класс a реализовывать методы из интерфейса b и c. Это означает, что если вы хотите наследовать поведение, вы должны агрегировать объекты-члены классов b и c в вашем классе a.
Франк

2
Подумайте об использовании декораторов sourcemaking.com/design_patterns/decorator или Strategies sourcemaking.com/design_patterns/strategy
Гордон,

2
Пожалуйста, рассмотрите черты как правильный ответ.
Даниэль

Ответы:


171

Отвечая на ваши изменения:

Если вы действительно хотите подделать множественное наследование, вы можете использовать магическую функцию __call ().

Это некрасиво, хотя работает с точки зрения пользователя класса А:

class B {
    public function method_from_b($s) {
        echo $s;
    }
}

class C {
    public function method_from_c($s) {
        echo $s;
    }
}

class A extends B
{
  private $c;

  public function __construct()
  {
    $this->c = new C;
  }

  // fake "extends C" using magic function
  public function __call($method, $args)
  {
    $this->c->$method($args[0]);
  }
}


$a = new A;
$a->method_from_b("abc");
$a->method_from_c("def");

Отпечатки "abcdef"


1
На самом деле мне очень нравится идея расширения класса. Есть ли какие-либо известные ограничения для этого?
atomicharri

3
Насколько я знаю, никаких ограничений нет, PHP - очень разрешающий язык для таких маленьких хаков. :) Как уже отмечали другие, это не самый лучший ООП способ сделать это.
Франк

64
Вы не сможете использовать защищенные или частные методы.
червоточина

1
@wormhit, однако, я бы не рекомендовал использовать его в производстве, можно использовать ReflectionClass для доступа к закрытым и защищенным методам.
Денис V

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

138

У вас не может быть класса, расширяющего два базовых класса. Вы не могли бы иметь.

// this is NOT allowed (for all you google speeders)
Matron extends Nurse, HumanEntity

Вы могли бы, однако, иметь иерархию следующим образом ...

Matron extends Nurse    
Consultant extends Doctor

Nurse extends HumanEntity
Doctor extends HumanEntity

HumanEntity extends DatabaseTable
DatabaseTable extends AbstractTable

и так далее.


Можете ли вы объяснить, почему правильно сначала наследовать Медсестру в Матрону, а затем объявить о наследовании HumanEntity в Медсестру?
Qwerty

7
@Qwerty Потому что у Матроны есть дополнительные качества медсестры, а у медсестры - все качества человека. Таким образом, Матрона - человек-медсестра и, наконец, обладает способностями Матроны
J-Dizzle

58

Вы можете использовать черты, которые, надеюсь, будут доступны из PHP 5.4.

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

Они признаны за их потенциал в поддержке лучшей композиции и повторного использования, следовательно, их интеграция в более новые версии языков, таких как Perl 6, Squeak, Scala, Slate и Fortress. Черты также были портированы на Java и C #.

Дополнительная информация: https://wiki.php.net/rfc/traits


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

2
Я не могу поверить, что это не выбранный ответ.
Даниэль

18

Классы не должны быть просто коллекциями методов. Предполагается, что класс представляет абстрактную концепцию с состоянием (полями) и поведением (методами), которое изменяет состояние. Использование наследования просто для получения желаемого поведения звучит как плохой дизайн ОО, и именно поэтому многие языки запрещают множественное наследование: чтобы предотвратить «наследование спагетти», то есть расширение 3 классов, потому что у каждого есть метод, который вам нужен, и заканчивается класс, который наследует 100 методов и 20 полей, но использует только 5 из них.


1
Я не согласен с вашим утверждением, что запрос OP представляет собой «плохой дизайн OO» ; просто взгляните на появление Mixins для поддержки позиции, согласно которой добавление методов в класс из нескольких источников является хорошей идеей в архитектурном отношении. Я, однако, скажу вам, что PHP не предоставляет оптимальный набор языковых функций для достижения оптимального «дизайна», но это не означает, что использование функций, доступных для аппроксимации, обязательно является плохой идеей; просто посмотрите на ответ @ Franck.
MikeSchinkel

15

Я думаю, что в ближайшее время планируется добавить дополнения.

Но до тех пор, иди с принятым ответом. Вы можете немного абстрагироваться, чтобы создать «расширяемый» класс:

class Extendable{
  private $extender=array();

  public function addExtender(Extender $obj){
    $this->extenders[] = $obj;
    $obj->setExtendee($this);
  }

  public function __call($name, $params){
    foreach($this->extenders as $extender){
       //do reflection to see if extender has this method with this argument count
       if (method_exists($extender, $name)){
          return call_user_func_array(array($extender, $name), $params);
       }
    }
  }
}


$foo = new Extendable();
$foo->addExtender(new OtherClass());
$foo->other_class_method();

Обратите внимание, что в этой модели «OtherClass» узнает о $ foo. OtherClass должен иметь открытую функцию setExtendee, чтобы установить это отношение. Затем, если его методы вызываются из $ foo, он может получить внутренний доступ к $ foo. Однако он не получит доступ к каким-либо закрытым / защищенным методам / переменным, как настоящий расширенный класс.


12

Используйте черты в качестве базовых классов. Затем используйте их в родительском классе. Продли это.

trait business{
  function sell(){

  }

  function buy(){

  }

  function collectMoney(){
  }

}

trait human{

   function think(){

   }

   function speak(){

   }

}

class BusinessPerson{
  use business;
  use human;
  // If you have more traits bring more
}


class BusinessWoman extends BusinessPerson{

   function getPregnant(){

   }

}


$bw = new BusinessWoman();
$bw ->speak();
$bw->getPregnant();

Увидеть сейчас бизнес-леди логически унаследовал бизнес и человека обоих;


Я застрял с PHP 5.3 нет поддержки черты: D
Nishchal Gautam

Почему бы не использовать черту BusinessWomanвместо BusinessPerson? Тогда вы можете иметь фактическое множественное наследование.
SOFe

@ SOFe, потому что BusinessMan также может расширить его. Таким образом, тот, кто когда-либо занимается бизнесом, может расширить деловую деятельность и получить черты автоматически
Алиса

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

Я верю, что до BusinessPerson это требовалось, BusinessWoman мог бы напрямую унаследовать другие черты. Но то, что я попробовал здесь, это сохранить обе черты в классе-посреднике, а затем использовать его там, где это необходимо. Так что я могу забыть о включении обеих черт, если мне снова понадобится кое-что еще (как я описал BusinessMan). Это все о стилях кода. Если вы хотите включить непосредственно в свой класс. Вы можете пойти дальше с этим - поскольку вы абсолютно правы в этом.
Алиса

9
<?php
// what if we want to extend more than one class?

abstract class ExtensionBridge
{
    // array containing all the extended classes
    private $_exts = array();
    public $_this;

    function __construct() {$_this = $this;}

    public function addExt($object)
    {
        $this->_exts[]=$object;
    }

    public function __get($varname)
    {
        foreach($this->_exts as $ext)
        {
            if(property_exists($ext,$varname))
            return $ext->$varname;
        }
    }

    public function __call($method,$args)
    {
        foreach($this->_exts as $ext)
        {
            if(method_exists($ext,$method))
            return call_user_method_array($method,$ext,$args);
        }
        throw new Exception("This Method {$method} doesn't exists");
    }


}

class Ext1
{
    private $name="";
    private $id="";
    public function setID($id){$this->id = $id;}
    public function setName($name){$this->name = $name;}
    public function getID(){return $this->id;}
    public function getName(){return $this->name;}
}

class Ext2
{
    private $address="";
    private $country="";
    public function setAddress($address){$this->address = $address;}
    public function setCountry($country){$this->country = $country;}
    public function getAddress(){return $this->address;}
    public function getCountry(){return $this->country;}
}

class Extender extends ExtensionBridge
{
    function __construct()
    {
        parent::addExt(new Ext1());
        parent::addExt(new Ext2());
    }

    public function __toString()
    {
        return $this->getName().', from: '.$this->getCountry();
    }
}

$o = new Extender();
$o->setName("Mahdi");
$o->setCountry("Al-Ahwaz");
echo $o;
?>

4
Ваш ответ должен содержать объяснение вашего кода и описание того, как он решает проблему.
AbcAeffchen

1
Это почти само за себя, но было бы здорово иметь комментарии в коде.
Heroselohim

теперь, если Ext1 и Ext2 имеют функцию с одинаковым именем, всегда вызывается Ext1, это не будет зависеть от параметров вообще.
Алиса

8

Принятый в настоящее время ответ @Franck будет работать, но на самом деле это не множественное наследование, а дочерний экземпляр класса, определенный вне области, также есть __call()сокращение - рассмотрите возможность использования $this->childInstance->method(args)любого метода класса ExternalClass в любом месте в «расширенном» классе.

Точный ответ

Нет, вы не можете, соответственно, не совсем, как говорится в руководстве по extendsключевым словам:

Расширенный класс всегда зависит от одного базового класса, то есть множественное наследование не поддерживается.

Реальный ответ

Однако, как правильно подсказал @adam, это НЕ запрещает вам использовать множественное иерархическое наследование.

Вы МОЖЕТЕ продлить один класс, с другим и другой с другим и так далее ...

Вот довольно простой пример:

class firstInheritance{}
class secondInheritance extends firstInheritance{}
class someFinalClass extends secondInheritance{}
//...and so on...

Важная заметка

Как вы могли заметить, множественное (2+) интегрирование по иерархии можно выполнять только в том случае, если у вас есть контроль над всеми классами, включенными в процесс - это означает, что вы не можете применить это решение, например, со встроенными классами или с классами, которые вы используете. просто не можете редактировать - если вы хотите это сделать, у вас остается решение @Franck - дочерние экземпляры.

... И, наконец, пример с некоторым выводом:

class A{
  function a_hi(){
    echo "I am a of A".PHP_EOL."<br>".PHP_EOL;  
  }
}

class B extends A{
  function b_hi(){
    echo "I am b of B".PHP_EOL."<br>".PHP_EOL;  
  }
}

class C extends B{
  function c_hi(){
    echo "I am c of C".PHP_EOL."<br>".PHP_EOL;  
  }
}

$myTestInstance = new C();

$myTestInstance->a_hi();
$myTestInstance->b_hi();
$myTestInstance->c_hi();

Какие выводы

I am a of A 
I am b of B 
I am c of C 

6

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

class C
{
    private $a, $b;

    public function __construct($x, $y)
    {
        $this->a = new A(42, $x);
        $this->b = new B($y);
    }

    protected function DoSomething()
    {
        $this->a->Act();
        $this->b->Do();
    }
}

Это фактически становится тем же, что показывают Франк и Сэм. Конечно, если вы решите явно использовать композицию, вы должны использовать внедрение зависимостей .
MikeSchinkel

3

Множественное наследование работает на уровне интерфейса. Я сделал тест на php 5.6.1.

Вот рабочий код:

<?php


interface Animal
{
    public function sayHello();
}


interface HairyThing
{
    public function plush();
}

interface Dog extends Animal, HairyThing
{
    public function bark();
}


class Puppy implements Dog
{
    public function bark()
    {
        echo "ouaf";
    }

    public function sayHello()
    {
        echo "hello";
    }

    public function plush()
    {
        echo "plush";
    }


}


echo PHP_VERSION; // 5.6.1
$o = new Puppy();
$o->bark();
$o->plush();
$o->sayHello(); // displays: 5.6.16ouafplushhello

Я не думал, что это было возможно, но я наткнулся на исходный код SwiftMailer, в классе Swift_Transport_IoBuffer, который имеет следующее определение:

interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream

Я еще не играл с ним, но подумал, что было бы интересно поделиться.


1
интересно и неактуально
Евгений Афанасьев

1

Я только что решил мою проблему «множественного наследования» с:

class Session {
    public $username;
}

class MyServiceResponsetype {
    protected $only_avaliable_in_response;
}

class SessionResponse extends MyServiceResponsetype {
    /** has shared $only_avaliable_in_response */

    public $session;

    public function __construct(Session $session) {
      $this->session = $session;
    }

}

Таким образом, у меня есть возможность манипулировать сессией внутри SessionResponse, который расширяет MyServiceResponsetype, по-прежнему способный обрабатывать Session сам по себе.


1

Если вы хотите проверить, является ли функция общедоступной, см. Эту тему: https://stackoverflow.com/a/4160928/2226755

И используйте метод call_user_func_array (...) для множества или не аргументов.

Как это :

class B {
    public function method_from_b($s) {
        echo $s;
    }
}

class C {
    public function method_from_c($l, $l1, $l2) {
        echo $l.$l1.$l2;
    }
}

class A extends B {
    private $c;

    public function __construct() {
        $this->c = new C;
    }

    public function __call($method, $args) {
        if (method_exists($this->c, $method)) {
            $reflection = new ReflectionMethod($this->c, $method);
            if (!$reflection->isPublic()) {
                throw new RuntimeException("Call to not public method ".get_class($this)."::$method()");
            }

            return call_user_func_array(array($this->c, $method), $args);
        } else {
            throw new RuntimeException("Call to undefined method ".get_class($this)."::$method()");
        }
    }
}


$a = new A;
$a->method_from_b("abc");
$a->method_from_c("d", "e", "f");


0

PHP пока не поддерживает множественное наследование классов, однако поддерживает множественное наследование интерфейсов.

См. Http://www.hudzilla.org/php/6_17_0.php для некоторых примеров.


Все же? Я сомневаюсь, что они сделают это. Современные ОО препятствуют множественному наследованию, оно может быть грязным.
Фил

0

PHP не допускает множественного наследования, но вы можете сделать это с помощью реализации нескольких интерфейсов. Если реализация "тяжелая", предоставьте скелетную реализацию для каждого интерфейса в отдельном классе. Затем вы можете делегировать весь интерфейсный класс этим реализациям скелета с помощью содержания объекта .


0

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


0

Всегда хорошая идея сделать родительский класс с функциями ... т.е. добавить всю эту функциональность в родительский.

И «переместить» все классы, которые используют это иерархически вниз. Мне нужно - переписать функции, которые являются конкретными.


0

Одна из проблем PHP как языка программирования заключается в том, что вы можете иметь только одно наследование. Это означает, что класс может наследоваться только от одного другого класса.

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

Эта проблема может привести к классу, у которого есть длинная семейная история наследования, которая часто не имеет смысла.

В PHP 5.4 была добавлена ​​новая функция языка, известная как Черты. Черта похожа на Mixin в том, что она позволяет вам смешивать классы черт в существующий класс. Это означает, что вы можете уменьшить дублирование кода и получить преимущества, избегая проблем множественного наследования.

Черты


-1

Это не настоящий ответ, у нас есть дубликат класса

... но это работает :)

class A {
    //do some things
}

class B {
    //do some things
}

класс copy_B - это копия всего класса B

class copy_B extends A {

    //do some things (copy class B)
}

class A_B extends copy_B{}

сейчас

class C_A extends A{}
class C_B extends B{}
class C_A_b extends A_B{}  //extends A & B

-3

класс А расширяет B {}

класс B расширяет C {}

Тогда А расширил и В, и С


2
@rostamiani, потому что этот метод также изменяет класс B. Что мы хотим , чтобы большую часть времени, чтобы иметь два несвязанных классов Bи C(вероятно , из различных библиотек) и получить одновременно наследуется A, таким образом, что Aсодержит все от обоих Bи C, но Bне содержит ничего от Cи наоборот.
SOFe
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.