PHP接口实现多重继承

17

我正在尝试理解使用接口如何给我提供多重继承,因为我一直在进行谷歌搜索。

class A
{
 function do1(){}
 function do2(){}
 function do3(){}
}

class B extends A
{
 function do4(){}
 function do5(){}
 function do6(){}
}

class C extends B
{
}
在上面的例子中,类C具有类A和B的所有方法。但是,类B也具有类A的所有方法,这不是必需的。
我的搜索结果表明,通过将方法移动到一个类并创建接口来解决此问题,如下所示。
interface A
{
     function do1();
     function do2();
     function do3();
}

interface B
{
     function do4();
     function do5();
     function do6();
}

class C implements A, B
{
     function do1(){}
     function do2(){}
     function do3(){}
     function do4(){}
     function do5(){}
     function do6(){}
}

我真的不太明白这如何解决问题,因为所有的代码都在新类中。如果我只想像原来一样使用A类,则必须创建一个实现接口A并将相同代码复制到新类中的新类。

我有什么遗漏吗?


你对C具有A的方法有什么具体的反对意见?这有什么问题吗?你的问题和目标不清楚。另外,你使用和针对哪个版本的PHP?现代PHP中有一些功能可以改变类的组成方式。 - Charles
@ShoShan,这个问题涉及到PHP中继承的语义。在这方面,PHP做了一些奇怪的事情,包括Traits的古怪、不正确的实现。 - Charles
1
C 有 A 类的方法本身没有问题,只是感觉不太对。我的目标是同时使用两个类的方法。我正在使用 5.3 版本。如果使用接口,问题归结为在不同类中重复代码。 - tdbui22
@tdbui22的评论“我的目标是使用两个类的方法”非常有意义,因为我认为你想要问的问题与你实际提出的问题有些不同。如果一个类扩展另一个类,那么扩展类也隐含地成为扩展类的实例。这就是面向对象编程中继承的工作原理(猫隐含地是哺乳动物,哺乳动物隐含地是脊椎动物,脊椎动物隐含地是动物等)。如果你只想使用一个类的方法而没有这种关系,那么意味着你真正想要的是组合而不是继承(has_a与is_a)。 - GordonM
5个回答

28

PHP没有多重继承。但是,如果你使用的是PHP 5.4,你可以使用traits来避免每个类都必须复制代码。

interface A {
    public function do1();
    public function do2();
    public function do3();
}

trait Alike {
    public function do1() { }
    public function do2() { }
    public function do3() { }
}


interface B {
    public function do4();
    public function do5();
    public function do6();
}

trait Blike {
    public function do4() { }
    public function do5() { }
    public function do6() { }
}


class C implements A, B {
    use Alike, Blike;
}

class D implements A {
    use Alike;

    // You can even "override" methods defined in a trait
    public function do2() { }
}

需要注意的是,您既必须实现接口又必须使用特性(或者自然地提供您自己的实现)。C和D除了都实现A接口外没有任何关系。特性基本上只是解释器级别的复制粘贴,不会影响继承。


24
了解接口的第一件事是,它们不用于继承。这一点非常重要。如果您试图让多个类共享相同的具体代码,那么接口就不是为此而设计的。
第二件需要了解的事情是客户端代码和服务端代码之间的区别。
客户端代码本质上是数据请求序列中的“最后一步”。MVC中的控制器或视图可以被认为是客户端代码。与此同时,模型可以被认为是服务端代码。
接口旨在强制执行客户端代码对从服务中获取的数据类型的一致性。或者可以这样想——接口是服务确保它们将与来自客户端代码的请求兼容的一种方式。它们确实提供了一种访问数据的接口,而不是多个类可以共享实现的接口。
因此,以下是一个具体的例子: 客户端代码——用户论坛资料的ProfileViewController类
class ProfileViewController
{
    public function showProfile(User $user)
    {
         $user->getProfile();
    }
}

Service Code - 一个用户模型,它检索数据并将其传递给请求它的客户端代码

class User
{
    public function getProfile()
    {
         $profile = Do some SQL query here or something
         return $profile;
    }
}

现在假设你之后决定将用户分为会员、管理员、裁判、调解员、作家、编辑等,并且每个人都有自己独特的个人资料类型。(例如,它拥有自己的自定义查询、数据或其他)

这里现在存在两个问题:

  1. 您需要确保无论传递什么,都将包含 getProfile() 方法。
  2. 如果传递的不是 User 对象,则 showProfile() 将失败。

1 很容易通过抽象类和方法(或通过接口)来解决。2 一开始也很容易解决,因为您可以将 Moderators、Admins 和 Members 都作为 User 基类的子类。

但是,如果在以后,除了用户资料之外,您还想要为事物创建通用资料怎么办。也许您想要显示运动员的档案,甚至是名人档案。他们不是用户,但他们仍然有个人资料/详情页。

因为他们不是用户,所以考虑将它们视为 User 的子类可能没有任何意义。

那么现在你有点困难了。showProfile() 需要能够接受的对象不仅仅是 User 对象。实际上,您不知道最终想要传递什么类型的对象。但同时,由于您始终想能够获得 $user->getProfile(),因此您传递的任何内容都必须足够通用,以便通过实现具体的 getProfile() 方法进行传递。

解决方案?接口!

首先是一些服务代码

// First define an interface for ANY service object that will have a profile

interface IHasProfile
{
    public function getProfile();
}


// Next, define the class for an object that should have a profile. I'll do a bunch for the sake of an example...

class User implements IHasProfile
{
    public function getProfile()
    {
         $profile = Your unique user profile query here
         return $profile;
    }
}


class Celebrity implements IHasProfile
{
    public function getProfile()
    {
         $profile = Your unique celebrity profile query here
         return $profile;
    }
}


class Car implements IHasProfile
{
    public function getProfile()
    {
         $profile = Your unique vehicle profile query goes here
         return $profile;
    }
}

接下来是使用它的客户端代码

class ProfileViewController
{
    public function showProfile(IHasProfile $obj)
    {
         $obj->getProfile();
    }
}

就是这样。showProfile()现在已经被抽象化到足够程度,它不关心得到的对象是什么,只关心该对象是否具有公共的getProfile()方法。因此,现在您可以尽情地创建新类型的对象,如果这些对象需要具有配置文件,则只需给它们“implements IHasProfile”,它们将自动与showProfile()配合使用。

这可能是一个人为的例子,但它至少说明了接口的概念。

当然,您也可以“懒惰”一点,根本不对对象进行类型转换,从而允许传入任何对象。但那是另一个完全不同的话题;)


1
顺便提一下,如果您处于想让单个类能够实现多个接口的位置,虽然从技术上讲是可能的,但并不建议这样做。这表明您的类正在尝试做太多的事情,并且将违反单一职责原则。 - AgmLauncher
我是新手...如何为ProfileViewController创建对象并调用showProfile方法,谢谢。 - jsdev

5

多重继承仅适用于接口!

例如我的输出结果:

php > interface A{};
php > interface B{};
php > interface C extends A,B{};
php > class D implements C{};
php > $d = new D();
php > echo ($d instanceof A);
1
  • 我创建了A和B接口,C接口继承它们。
  • 之后,我们有一个实现C接口的D类
  • 最后,我询问$d对象是否是A接口的实例,是的,这是真的

为了好玩,我尝试创建一个扩展D和stdclass类的E类,结果出错了!

php > class E extends D, stdclass{};
PHP Parse error:  syntax error, unexpected ',', expecting '{' in php shell code on line 1

Parse error: syntax error, unexpected ',', expecting '{' in php shell code on line 1

我不确定我理解了。您展示了一堆错误...有没有成功实现的方法? - NickSentowski
1
@NickSentowski 我编辑了我的帖子,并将错误与示例分开。 - tonicospinelli

1

PHP不支持像许多支持面向对象编程的语言一样的多重继承。

请参阅类似主题此处。该主题是关于AS3的,但可以给您答案。

有关使用接口解决的特定答案在同一篇文章中回答此处


Java作为一种面向对象的语言,也不支持多重继承 - 这真是遗憾 :-( - Flukey

0

正如@tonicospinelli在这里所说,PHP确实允许接口的多重继承,但并没有明确解释,只是给出了一个例子

多重继承的方式是,PHP使用实现接口的特征(Traits)来传递这些接口。

一旦你声明了一个实现“多接口”的类(1),你可以使用已经定义好的特征来确保继承得到良好执行。

(1): 所谓“多接口”,是指一个类实现了一个继承自多个其他接口的接口。


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