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

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

то есть class a extends b extends c

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

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

Franck 10.12.2008 17:19

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

atomicharri 10.12.2008 17:32

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

Franck 10.12.2008 17:37

Я имею в виду, поместите частный $ b (экземпляр b) и частный $ c (экземпляр c) в свой класс a, если это было недостаточно ясно.

Franck 10.12.2008 17:40

Рассмотрите возможность использования декораторов sourcemaking.com/design_patterns/decorator или стратегий sourcemaking.com/design_patterns/strategy

Gordon 22.04.2010 14:00

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

Halfstop 22.04.2016 17:33

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

Daniel 26.06.2018 12:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
159
7
258 716
20
Перейти к ответу Данный вопрос помечен как решенный

Ответы 20

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

См. Некоторые примеры в http://www.hudzilla.org/php/6_17_0.php.

Все же? Я сомневаюсь, что они это сделают. Современные объектно-ориентированные объекты не поощряют множественное наследование, оно может быть беспорядочным.

PhiLho 10.12.2008 19:53

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

// 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

и так далее.

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

Qwerty 26.02.2014 22:01

@Qwerty Потому что у Матрона есть дополнительные качества медсестры, а у медсестры - все качества человека. Следовательно, Матрона - человек-медсестра и, наконец, имеет возможности Матрона.

J-Dizzle 20.09.2014 00:59

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

Я не согласен с вашим утверждением, что запрос OP составляет "плохой объектно-ориентированный дизайн"; просто посмотрите на появление Миксины, чтобы поддержать позицию, согласно которой добавление методов к классу из нескольких источников является хорошей архитектурной идеей. Тем не менее, я сообщу вам, что PHP не обеспечивает оптимальный набор языковых функций для достижения оптимального «дизайна», но это не означает, что использование доступных функций для его аппроксимации обязательно является плохой идеей; просто посмотрите на ответ @Franck.

MikeSchinkel 29.10.2013 21:09
Ответ принят как подходящий

Если вы действительно хотите имитировать множественное наследование в PHP 5.3, вы можете использовать волшебную функцию __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"

На самом деле мне очень нравится идея расширения класса. Существуют ли какие-либо известные ограничения в том, чтобы делать это таким образом?

atomicharri 10.12.2008 21:12

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

Franck 10.12.2008 21:58

Мне нравится эта идея, чистая и простая!

Max Kielland 11.01.2011 05:44

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

wormhit 16.11.2012 18:20

Есть комментарий, который предлагает способ сделать это в php doc, с тем же ограничением для защищенных и частных методов php.net/manual/fr/keyword.extends.php#98665

kaore 13.08.2013 12:20

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

Denis V 18.12.2013 23:21

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

Erik 14.04.2015 16:51

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

Plixxer 06.05.2018 23:39

@wormhit, чтобы сохранить защищенную или частную видимость, вам нужно будет расширить расширенный класс. В большинстве случаев это не идеально.

Plixxer 06.05.2018 23:43

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

Я прочитал несколько статей, не одобряющих наследование в проектах (в отличие от библиотек / фреймворков) и поощряющих программирование интерфейсов, не противоречащих реализации. Они также защищают объектно-ориентированный подход по составу: если вам нужны функции в классе a и b, сделайте 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 29.10.2013 21: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 изнутри. Однако он не получит доступа к каким-либо частным / защищенным методам / переменным, как настоящий расширенный класс.

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

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

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

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

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

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

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

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

Nikola Petkanski 10.02.2017 17:20

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

Daniel 26.06.2018 12:57

Я только что решил проблему с "множественным наследованием":

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, по-прежнему имея возможность обрабатывать сеанс самостоятельно.

Если вы хотите проверить, является ли функция общедоступной, см. Эту тему: 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");
<?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;
?>

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

AbcAeffchen 07.10.2014 04:36

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

Heroselohim 11.03.2016 21:26

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

Alice 25.06.2020 13:50

Обновлено: 2020 PHP 5.4+ и 7+

Начиная с PHP 5.4.0 есть "Черты" - вы можете использовать больше трейтов в одном классе, поэтому окончательный момент принятия решения будет заключаться в том, хотите ли вы действительно наследство или вам просто нужен какая-то "особенность" (черта). Тенденция - это, расплывчато сказано, уже реализованный интерфейс, который должен быть просто used.


Currently accepted answer by @Franck will work but it is not in fact multiple inheritance but a child instance of class defined out of scope, also there is the `__call()` shorthand - consider using just `$this->childInstance->method(args)` anywhere you need ExternalClass class method in "extended" class.

Точный ответ

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

An extended class is always dependent on a single base class, that is, multiple inheritance is not supported.

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

Однако, как правильно предположил @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 

Вы можете сделать это с помощью Traits в PHP, объявленных в PHP 5.4.

Вот краткое руководство для вас, http://culttt.com/2014/06/25/php-traits/

Множественное наследование работает на уровне интерфейса. Сделал тест на 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

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

интересно и неактуально.

Yevgeniy Afanasyev 24.12.2019 06:34

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

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

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

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

Черты

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

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

user1267177 20.02.2018 05:24

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

SOFe 30.01.2019 19:54

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

Alice 06.02.2019 16:09

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

SOFe 06.02.2019 16:53

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

Alice 06.02.2019 17:01

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

SOFe 14.02.2019 15:23

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

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

Тогда A расширил и B, и C

@rostamiani, потому что этот метод также изменяет класс B. В большинстве случаев мы хотим иметь два несвязанных класса B и C (вероятно, из разных библиотек) и одновременно унаследоваться от A, так что A содержит все как от B, так и от C, но B не содержит ничего из C и наоборот.

SOFe 30.01.2019 19:51

Это не настоящий ответ, у нас есть повторяющийся класс

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

class A {
    //do some things
}

class B {
    //do some things
}

class 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

Другие вопросы по теме