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

179
如果我有几个带有所需功能的类,但想要单独进行存储以进行组织,那么我可以扩展一个类来同时具有吗?
class a extends b extends c 编辑:我知道如何逐个扩展类,但我正在寻找一种通过多个基类即时扩展类的方法-据我所知,在PHP中无法实现这一点,但应该有不需要诉诸class c extends bclass b extends a的方法来解决。

2
我正在研究接口,因为我不太喜欢庞大的类层次结构。但是我看不出接口实际上有什么作用? - atomicharri
1
接口使您仅“继承”API,而不是方法体。它强制类a实现来自接口b和c的方法。这意味着如果您想要继承行为,则必须在您的类a中聚合类b和c的成员对象。 - Franck
2
考虑使用装饰器 http://sourcemaking.com/design_patterns/decorator 或策略模式 http://sourcemaking.com/design_patterns/strategy。 - Gordon
如果我认为多重继承是解决问题的最佳方案,我会质疑我的设计。我曾沿着这条路走过,并遇到了这个问题。通常情况下,如果您试图做一些语言自然不支持的事情,您应该质疑您的设计。多重继承似乎过于混乱和笨重。 - Halfstop
3
请将traits视为正确答案。 - Daniel
显示剩余2条评论
18个回答

189

如果你真的想在 PHP 5.3 中模拟多重继承,你可以使用魔术函数 __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是一种非常宽容的语言,适合进行小型hack。:) 正如其他人指出的那样,这并不是正确的面向对象编程方式。 - Franck
74
您将无法使用受保护或私有方法。 - wormhit
1
@wormhit,虽然我不建议在生产中使用,但可以使用ReflectionClass来访问私有和受保护的方法。 - Denis V
2
这对于期望方法实际存在的 Implements 不起作用,当多个基类具有相同名称的方法时,行为几乎没有定义。它还会破坏您编辑器提供提示的能力。 - Erik
显示剩余4条评论

153

一个类不能同时继承两个基类。以下代码是不合法的:

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

等等。


1
你能解释一下为什么首先将Nurse继承到Matron,然后再声明将HumanEntity继承到Nurse吗? - Qwerty
8
因为护士长具备护士的所有素质,再加上其他特质,而普通护士只有人类的素质,因此护士长是一名人类护士,最终拥有护士长的能力。 - J-Dizzle

75

你可以使用Traits,希望这个特性能够在PHP 5.4及以上版本中使用。

Traits是一种代码重用机制,适用于单一继承的语言,例如PHP。Trait旨在通过使开发人员能够在不同类层次结构中的多个独立类中自由重用方法集来减少单一继承的某些限制。Traits和类的组合语义被定义为一种方式,以降低复杂性并避免与多重继承和Mixins相关的典型问题。

它们因其在支持更好的组成和重用方面的潜力而得到认可,因此它们被集成到Perl 6、Squeak、Scala、Slate和Fortress等新版本语言中。Traits也被移植到Java和C#中。

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


确保不要滥用它们。本质上,它们是应用于对象的静态函数。滥用它们可能会导致混乱。 - Nikola Petkanski
3
我无法相信这不是被选中的答案。 - Daniel
1
我也是。 - Alexander Behling
这应该被选为答案,因为现在它被认为是标准。 - MaXi32

22

编辑:2020 PHP 5.4+ and 7+

从PHP 5.4.0开始,有“Traits”——您可以在一个类中使用多个trait,因此最终决定点将是您是否需要真正的继承或者只需要一些“特性”(trait)。Trait模糊地说是一个已经实现的接口,它只是用于use


目前由@Franck接受的答案可以工作,但实际上它不是多重继承而是定义在范围之外的子类实例,还有`__call()`简写-请考虑仅在需要“扩展”类中的ExternalClass类方法时使用`$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 

1
这需要更多的投票,因为在大多数情况下,特征将是人们需要的实现! - Peon

19

类不应只是一些方法的集合。类应该代表一个抽象概念,既有状态(字段),也有行为(方法),从而改变状态。仅仅使用继承来获得某些期望的行为听起来像是糟糕的面向对象设计,这也是为什么许多语言禁止多重继承的原因:为了防止“意大利面式继承”,例如,扩展3个类,因为每个类都有你需要的方法,结果得到一个继承100个方法和20个字段的类,但只使用其中的5个。


1
我对你的看法有异议,认为OP的请求并不构成“糟糕的OO设计”。只需看一下Mixin的出现便可支持这样的立场,即从多个来源向类添加方法在构架上是一个好主意。然而我要承认PHP并没有提供最优秀的语言特性来实现最佳的“设计”,但这并不意味着使用可用的特性来近似它就一定是一个糟糕的主意。可以看看@Franck的回答。 - MikeSchinkel

18

将 traits 用作基类。然后在父类中使用它们。扩展它。

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 中,没有 trait 支持 :D - user1267177
@SOFe,因为BusinessMan也可以扩展它。 所以,无论谁在做生意,都可以扩展商业人士,并自动获得特征。 - Alice
你现在的做法与单继承没有什么区别,其中BusinessPerson扩展了一个名为human的抽象类。我的观点是,你的例子确实不需要多继承。 - SOFe
1
我认为在BusinessPerson之前是必需的,BusinessWoman可以直接继承两个特征。但是我在这里尝试的是将两个特征保留在中介类中,然后在需要时使用它。因此,如果再次需要某个地方(如我所描述的BusinessMan),我就可以忘记包含这两个特征。这完全是关于代码风格的问题。如果您喜欢直接将其包含在您的类中,则可以继续进行-因为您对此完全正确。 - Alice
更新2021年:BusinessNonBinary扩展了BusinessPerson。 - dewd
显示剩余2条评论

18

据我所知,很快就会有添加混合项的计划。

但在那之前,请按照被接受的答案来进行。您可以将其抽象出一点,以创建一个“可扩展”的类:

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。但是,它将不会像真正的扩展类一样获得对任何私有/受保护方法/变量的访问权限。


10
<?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,而不会考虑参数。 - Alice

6

我读过几篇文章,反对在项目中使用继承(相对于库/框架),并鼓励针对接口编程而不是实现。
他们还提倡通过组合实现面向对象编程:如果需要类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();
    }
}

这实际上与Franck和Sam展示的内容相同。当然,如果您选择明确使用组合,则应使用依赖注入。 - 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
有趣且不相关。 - Yevgeniy Afanasyev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接