Чи можу я розширити клас, використовуючи більше 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
Gordon

2
Будь ласка, розгляньте риси як правильну відповідь.
Даніель

Відповіді:


170

Відповідь на вашу редакцію:

Якщо ви дійсно хочете підробити багаторазове успадкування, ви можете скористатися магічною функцією __call ().

Це некрасиво, хоча працює з точки зору користувача A:

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 - це дуже дозвільна мова для маленьких хаків. :) Як інші зазначали, однак це не належний спосіб OOP робити це.
Франк

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

Класи не мають бути лише сукупністю методів. Клас повинен представляти абстрактне поняття, яке змінює стан як із станом (полями), так і з поведінкою (методами). Використання спадкування лише для отримання бажаної поведінки звучить як поганий дизайн OO, і саме тому, чому багато мов забороняють багатократне успадкування: з метою запобігання "успадкуванню спагетті", тобто розширення 3 класів, оскільки кожен має потрібний вам спосіб, і закінчується клас, який успадковує 100 методів та 20 полів, але лише будь-коли використовує 5 з них.


1
Я буду сумніватися з вашим твердженням, що запит ОП є "поганим дизайном ОО" ; просто подивіться на появу Mixins, щоб підтримати позицію, що додавання методів до класу з кількох джерел є гарною ідеєю архітектурно. Однак я скажу вам, що PHP не забезпечує оптимального набору мовних функцій для досягнення оптимального "дизайну", але це не означає, що використання доступних функцій для наближення це обов'язково погана ідея; просто подивіться на відповідь @ Франка.
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
Ніщал Гаутам

Чому б не використовувати ознаку 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
Це майже не пояснює, але було б чудово мати коментарі уздовж коду.
Героселохім

Тепер, якщо у 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 інтерфейси, не проти реалізації.
Вони також захищають ОО за складом: якщо вам потрібні функції в класах 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

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 і досі може самостійно обробляти сесію.


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 для деяких прикладів.


І все-таки? Сумніваюся, вони це зроблять. Сучасні ОО відбивають багаторазове успадкування, воно може бути безладно.
PhiLho

0

PHP не дозволяє множинне успадкування, але ви можете зробити з реалізацією декількох інтерфейсів. Якщо реалізація є "важкою", надайте скелетну реалізацію для кожного інтерфейсу в окремому класі. Потім ви можете делегувати весь клас інтерфейсу до цих скелетних реалізацій за допомогою об'єкта .


0

Не знаючи точно, чого ви намагаєтеся досягти, я б запропонував розглянути можливість переробити додаток для використання композиції, а не спадкування в цьому випадку.


0

Завжди гарною ідеєю є створення батьківського класу, з функціями ... тобто додайте цю всю функціональність до батьківського.

І "перемістіть" усі класи, які використовують цю ієрархічну форму вниз. Мені потрібно - переписати функції, які є специфічними.


0

Однією з проблем PHP як мови програмування є той факт, що ви можете мати лише одне успадкування. Це означає, що клас може успадковувати лише один клас.

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

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

У PHP 5.4 була додана нова особливість мови, відома як «Риси». Характер схожий на Mixin тим, що дозволяє змішувати класи Trait у існуючий клас. Це означає, що ви можете зменшити дублювання коду та отримати переваги, уникаючи проблем багаторазового успадкування.

Риси


-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

клас A поширюється на 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.