带接口的 PHP 多重继承

2022-08-31 00:01:16

我试图理解如何使用接口给我多重继承,因为我一直在谷歌搜索。

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 的新类,并将相同的代码复制到新类中。

我错过了什么吗?


答案 1

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 接口。特征基本上只是解释器级别的复制和粘贴,并且不影响继承。


答案 2

关于接口,首先要了解的是它们不用于继承。这是一件非常重要的事情要理解。如果您尝试使多个类共享相同的具体代码,则这不是接口的用途。

要了解的第二件事是客户端代码和服务代码之间的区别。

客户端代码本质上是一系列数据请求中的“最后一步”。MVC 中的控制器或视图可以被视为客户端代码。同时,该模型可以被视为服务代码。

接口旨在供客户端代码使用,以强制实现从服务获取的数据类型的一致性。或者另一种思考方式 - 接口是服务确保它们与来自客户端代码的请求兼容的一种方式。这就是他们所做的一切。从字面上看,它们提供了一个访问数据的接口,而不是多个类可以共享的实现。

举个具体的例子:

客户端代码 - 用于用户论坛配置文件的 ProfileViewController 类

class ProfileViewController
{
    public function showProfile(User $user)
    {
         $user->getProfile();
    }
}

服务代码 - 一种用户模型,用于检索数据并将其传递给请求数据的客户端代码

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

现在假设稍后您决定将用户分解为成员,管理员,裁判,版主,作家,编辑等,并且每个人都有自己独特的个人资料类型。(例如,它自己的自定义查询,或数据,或者你有什么)

现在这里存在两个问题:

  1. 您需要保证您传入的任何内容都将包含 getProfile() 方法。
  2. showProfile() 如果传入 User 对象以外的任何内容,则将失败。

1很容易通过抽象类和方法(或通过接口)解决。2起初听起来也很容易,因为您只需将版主,管理员和成员设置为用户基类的所有子类即可。

但是,除了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() 方法。所以现在你可以根据你心中的内容创建新的对象类型,如果它们打算具有配置文件,你可以给它们“实现IHasProfile”,它们将自动与showProfile()一起使用。

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

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


推荐