【问题标题】:PHP OOP Declaration of interface implementation compatibilityPHP OOP 接口实现兼容性声明
【发布时间】:2014-02-09 22:16:11
【问题描述】:

我有以下 OOP 结构:

<?php


interface AnimalInterface
{
    public function getName();
}

class Dog implements AnimalInterface
{
    public function getName() {
        return 'dog';
    }

    public function makeFriends()
    {
        echo 'I have friends now :)';
    }
}

class Cat implements AnimalInterface
{
    public function getName() {
        return 'cat';
    }

    public function hateFriends()
    {
        echo 'I cant make friends :(';
    }
}

interface AnimalDoInterface
{
    public function makeFriend(AnimalInterface $animal);
}

class DogFriend implements AnimalDoInterface
{
    public function makeFriend(Dog $dog)
    {
        $dog->makeFriends();
    }
}

class CatFriend implements AnimalDoInterface
{
    public function makeFriend(Cat $cat)
    {
        $cat->hateFriends();
    }
}

现在Object Interfaces 上的 PHP 手册说:

实现接口的类必须使用与接口中定义的完全相同的方法签名。不这样做会导致致命错误。

为什么会这样?我完全误解了接口吗?当然,我应该能够使用任何接口或该接口的实现来声明AnimalDoInterface::makeFriend?在这种情况下,它在技术上应该是兼容的,因为 Cat 实现了 AnimalInterface,这是它所期望的。

不管我的 OOP 是否出错,有没有办法在 PHP 中实现它?

所以看来我不够清楚,我的错。但是,基本上我想要实现的是让AnimalDoInterface 的实现比它的接口说的更严格。所以在这种情况下,我希望DogFriend::makeFriend 只允许Dog 类作为它的参数,在我看来这应该是可以接受的,因为它实现了AnimalInterface,而CatFriend 允许Cat类,这又是一回事。

编辑:修复了类,还添加了我想要实现的目标。

编辑 2:

所以目前,我必须实现它的方式如下:

class DogFriend implements AnimalDoInterface
{
    public function makeFriend(AnimalInterface $dog)
    {
        if(!($dog instanceof Dog)) {
            throw new \Exception('$dog must be of Dog type');
        }
        $dog->makeFriends();
    }
}

class CatFriend implements AnimalDoInterface
{
    public function makeFriend(AnimalInterface $cat)
    {
        if(!($dog instanceof Cat)) {
            throw new \Exception('$dog must be of Cat type');
        }
        $cat->hateFriends();
    }
}

我希望避免对类类型进行这种额外检查。

【问题讨论】:

  • 我不认为我在关注你所说的任何事情。实际问题是什么? “方法签名”是指方法名称、参数(带有类型提示)、可见性(公共/私有)以及它是否是静态的。
  • 不完全清楚你在问什么。您是否尝试运行上面的代码? Fatal error: Class Dog contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (AnimalInterface::getAnimalName) 会出错
  • 实现的AnimalInterface 类没有抽象的方法getAnimalName()。并且必须使用。请记住,所有接口方法本身都是抽象的,必须在类中实现
  • 接口是一种契约,保证将使用实现该接口的类的实例的任何人实现一组具有特定签名的方法。如果您需要两组功能,而不是在您的类之间共享,则将签名分隔在两个单独的接口中。
  • 我的错,修复了 PHP 以转到正确的错误,而不是 @MichaelBerkowski 所说的。

标签: php oop interface


【解决方案1】:

接口的唯一工作是强制两个对象以相同的方式行为这一事实,而不管它们如何实现该行为。这是一份合同,规定两个对象可以出于某些特定目的互换。

(编辑:这部分代码已经更正,但作为一个很好的介绍。) 接口AnimalInterface 定义了行为(函数)getAnimalName(),以及任何声称实现该接口必须实现该行为。 class Dogimplements AnimalInterface 声明,但没有实现所需的行为 - 您不能在 Dog 的实例上调用 getAnimalName()。所以我们已经有一个致命错误,因为我们没有满足接口定义的“合同”。

修复该问题并继续,然后您将拥有一个接口 AnimalDoInterface,它具有 makeFriend(AnimalInterface $animal) 的定义行为(功能) - 意思是,您可以将实现 AnimalInterfaceany 对象传递给实现AnimalDoInterfaceany 对象的makeFriend 方法。

但是你随后用更严格的行为定义了class DogFriend——它的makeFriend 版本只能接受Dog 对象;根据接口,它应该也可以接受Cat对象,这也实现了AnimalInterface,所以再次,接口的“契约”没有得到满足,我们会得到一个致命的错误。

如果我们要解决这个问题,您的示例中会有一个不同的问题:您调用了$cat-&gt;hateFriends();,但如果您的参数类型为AnimalInterfaceAnimalDoInterface,您将无法知道存在 hateFriends() 函数。 PHP,对这些事情非常放松,如果它根本不存在,会让你尝试并在运行时爆炸;更严格的语言只允许您使用保证存在的函数,因为它们是在接口中声明的。


要理解为什么你不能比接口更严格,假设你不知道特定对象的类,你只知道它实现了特定接口。 p>

如果我知道对象$a 实现AnimalInterface,并且对象$b 实现AnimalDoInterface,我可以通过查看接口做出以下假设:

  1. 我可以打电话给$a-&gt;getName();(因为AnimalInterface在它的合同中有这个)
  2. 我可以打电话给$b-&gt;makeFriend($a);(因为AnimalDoInterface在它的合同中规定我可以传递任何实现AnimalInterface的东西)

但是对于您的代码,如果 $aCat,而 $bDogFriend,我的假设 #2 将失败。如果界面让这种情况发生,它就无法发挥作用。

【讨论】:

  • 谢谢,所以第一个问题不是问题,第二个问题才是我的实际问题。我更新了我的问题以解释我想要实现的目标。
  • @HoshSadiq 我添加了一个额外的部分,可以更清楚地说明为什么接口不能让你做你正在尝试的事情。
  • 我明白这一点,但肯定应该可以使某个参数更具限制性,这样使实现更具限制性的类,即在这种情况下DogFriend,那么它仍然知道它有它的“合同”方法,还有其他特定于Dog 的方法?请参阅我编辑的问题,尽管它并不理想,但它是一种解决方法,尽管在我看来是一个丑陋的解决方法。根据你的说法,根本不应该是这样,还是我弄错了?
  • @HoshSadiq 您是从DogFriend 的角度考虑的,而不是代码使用 DogFriend。接口的重点是提前知道可以用特定对象做什么;如果一个对象可以声称实现AnimalDoInterface,但不允许Cats,那么我阅读该接口没有意义——我无法知道我可以在不知道细节的情况下传入什么样的对象特定的类。如果你想改变函数签名,不要使用接口来声称它们是相同的。
  • 我想很公平。谢谢你的解释:)
【解决方案2】:

实现接口的所有类都必须具有相同方法的原因是,您可以在对象上调用该方法,而不管实例化了哪个子类型。

在这种情况下,您的逻辑不一致是因为两个子类型 Cat 和 Dog 有不同的方法。所以你不能在对象上调用makeFriends(),因为你不知道对象那个方法。

这就是使用接口的意义所在,所以你可以使用不同的子类型,但同时你至少可以确定一些常用的方法。

在您的情况下处理此问题的一种方法是确保 Cat 实现相同的方法,但使该方法在运行时抛出异常,表明这是不可能的。这允许在编译时满足接口(我知道 PHP 没有编译器,但这是在模仿 Java 等在编译时进行接口检查的语言)。

class Cat implements AnimalInterface
{
    public function makeFriends()
    {
        throw new RuntimeException('I cant make friends :(');
    }
}

【讨论】:

  • PHP 肯定有一个编译器,并且确实会在编译时进行这样的检查,如果你 include 一个文件中有一个错误的 class 定义,就可以看出这一点。 (您只是不手动调用编译器,它通常会在每次加载文件时重新处理文件,除非您使用“操作码缓存”。)
  • 抱歉,请查看更新的问题,我不小心发布了一个不完整的问题 :)
【解决方案3】:

实现AnimalDoInterface 的类必须有一个makeFriend 方法,该方法采用任何实现AnimalInterface 的对象。在您的情况下,尝试声明

class DogFriend implements AnimalDoInterface {
  public function makeFriend(Dog $foo) { }
}

不会准确地实现这一点,因为将任何实现 AnimalInterface 的东西传递给任何实现 AnimalDoInterface 的东西的 makeFriend 方法应该总是安全的。

【讨论】:

    猜你喜欢
    • 2013-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-09
    • 1970-01-01
    相关资源
    最近更新 更多