我可以在PHP中使用多个类来扩展一个类吗?


150

如果我有几个具有所需功能的类,但想单独存储以便组织使用,我可以扩展一个类以同时拥有这两个类吗?

class a extends b extends c

编辑:我知道如何一次扩展一个类,但是我正在寻找一种使用多个基类立即扩展一个类的方法-AFAIK您无法在PHP中做到这一点,但是应该有一些解决方法,而不必诉诸于此class c extends bclass b extends a


使用聚合或接口。PHP中不存在多重继承。
弗兰克

2
我正在研究接口,因为我不喜欢大型类层次结构。但是我看不到接口实际上是如何工作的?
—atomicharri

接口使您只能“继承” API,而不能“继承” API。它强制类a从接口b和c实现方法。这意味着,如果要继承行为,则必须在类a中聚合类b和c的成员对象。
弗兰克


2
请考虑将特征作为正确答案。
丹尼尔(Daniel)

Answers:


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
您将无法使用受保护的方法或私有方法。
wormhit 2012年

1
@wormhit,但是,我不建议在生产中使用它,可以使用ReflectionClass来访问私有和受保护的方法。
Denis V

2
这对于Implements无效,后者希望该方法实际存在,并且当多个基类具有相同名称的方法时,行为几乎是不确定的。它还会破坏编辑器给您提示的能力。
Erik

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

等等。


您能解释一下为什么先将Nurse继承到Matron,然后将HumanEntity继承声明为Nurse是正确的吗?
Qwerty

7
@Qwerty因为Matron具有护士的其他特质,而护士却具有人类的所有特质。因此,Matron是一名人类护士,最终拥有Matron功能
J-Dizzle 2014年

58

您可以使用特征,希望可以从PHP 5.4中获得。

特性是一种在PHP等单一继承语言中重用代码的机制。特性旨在通过使开发人员在生活在不同类层次结构中的几个独立类中自由重用方法集,从而减少单一继承的某些限制。特性和类的组合的语义以某种方式定义,这降低了复杂性并避免了与多重继承和Mixins相关的典型问题。

它们在支持更好的组合和重用方面具有潜力,因此被公认为Perl 6,Squeak,Scala,Slate和Fortress等较新版本的语言的集成。特性也已移植到Java和C#。

更多信息:https : //wiki.php.net/rfc/traits


确保不要滥用它们。本质上,它们是您应用于对象的静态函数。您可以通过滥用它们来制造混乱。
尼古拉·佩特坎斯基

2
我不敢相信这不是选择的答案。
丹尼尔(Daniel)

18

类并不意味着仅仅是方法的集合。一个类应该代表一个抽象的概念,状态(字段)和行为(方法)都会改变状态。使用继承只是为了获得某些期望的行为听起来像是糟糕的OO设计,而正是许多语言不允许多重继承的原因:为了防止“意大利面条继承”,即扩展3个类,因为每个类都有您需要的方法,最后得到一个继承100个方法和20个字段的类,但只使用了其中的5个。


1
我会质疑您的主张,即OP的请求构成 “不良的OO设计”;只要看看Mixins的出现来支持这种观点,即从多个来源向类添加方法在架构上是个好主意。但是,我将为您提供PHP并没有提供一组最佳的语言功能来实现最佳的“设计”,但这并不意味着使用可用的功能来近似它就不一定是个好主意;只要看看@Franck的答案。
MikeSchinkel 2013年

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 '18

为什么不使用trait BusinessWoman代替BusinessPerson?然后,您可以进行实际的多重继承。
SOFe

@SOFe,因为BusinessMan也可以扩展它。因此,做生意的人可以扩大业务范围,并自动获得特质
爱丽丝

您执行此操作的方式与BusinessPerson扩展一个名为human的抽象类的单继承没有什么不同。我的观点是,您的示例实际上并不需要多重继承。
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 2014年

1
这几乎是不言而喻的,但是在代码中附带注释会很棒。
Heroselohim

现在,如果Ext1和Ext2都具有相同名称的函数,则始终将调用Ext1 c,则它将完全不依赖于参数。
爱丽丝

8

@Franck当前接受的答案将起作用,但是实际上它不是多重继承,而是超出范围定义的类的子实例,还有一种__call()简写方式-考虑$this->childInstance->method(args)在“扩展”类中仅使用 需要外部类方法的任何地方。

确切答案

不,您不能,分别不是,正如关键字的手册所述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

我读过几篇文章,这些文章不鼓励在项目中进行继承(相对于库/框架),并鼓励对不可靠的接口进行编程,而不反对实现。
他们还通过构成提倡OO:如果您需要类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 2013年

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
有趣而无关紧要的。
Yevgeniy Afanasyev

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不允许多重继承,但是您可以实现多个接口。如果实现“繁重”,请在单独的类中为每个接口提供基本实现。然后,您可以通过对象包含将所有接口类委托给这些骨架实现


0

我不完全知道您要实现的目标,因此建议您考虑重新设计应用程序以使用合成而不是继承的可能性。


0

总是好主意是使父类具有函数……即,将所有这些功能添加到父类。

并“向下移动”使用该层次结构的所有类。我需要-重写特定的功能。


0

PHP作为一种编程语言的问题之一是您只能拥有单一继承这一事实。这意味着一个类只能从另一个类继承。

但是,很多时候从多个类继承是有益的。例如,可能希望从几个不同的类中继承方法,以防止代码重复。

这个问题可能导致具有悠久家族遗传史的阶级,这往往是没有意义的。

在PHP 5.4中,增加了该语言的新功能,称为特性。Trait有点像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 {}

然后A扩展了B和C


2
@rostamiani,因为此方法还在修改class B。我们大多数时候想要的是拥有两个不相关的类,B并且C(可能来自不同的库)并同时被继承A,这样就A包含了B和的所有内容C,但B不包含C和的任何内容,反之亦然。
SOFe
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.