PHP接口和参数继承

时间:2015-07-27 15:33:51

标签: php interface extend

我的项目中有实体和存储库。为了简化,我有

  • EntityInterface
  • UserEntity
  • BusinessEntity

接口

interface Entity
{
    /**
     * @return EntityId
     */
    public function getId();
}

实现

class UserEntity implements Entity
{
    /**
     * @return EntityId
     */
    public function getId(){
      //...do something here for return
      return $userId;
    }
}

class BusinessEntity implements Entity
{
    /**
     * @return EntityId
     */
    public function getId(){
      //...do something here for return
      return $userId;
    }
}

我想定义一个Repository基本功能,比如save,所以我的界面如下:

interface Repository
{
    /**
     * @param Entity $entity
     *
     * @throws \InvalidArgumentException If argument is not match for the repository.
     * @throws UnableToSaveException If repository can't save the Entity.
     *
     * @return Entity The saved entity
     */
    public function save(Entity $entity);
}

稍后,我为不同类型的存储库提供了不同的界面,例如UserRepositoryBusinessRepository

interface BusinessRepository extends Repository
{
    /**
     * @param BusinessEntity $entity
     *
     * @throws \InvalidArgumentException If argument is not match for the repository.
     * @throws UnableToSaveException If repository can't save the Entity.
     *
     * @return Entity The saved entity
     */
    public function save(BusinessEntity $entity);
}

上述代码失败,因为Declaration must be compatible with Repository...

然而,BusinessEntity实现了实体,因此它是兼容的。

我有很多类型的实体,所以如果我不能输入提示,我总是需要检查,传递的实例是我需要的实例。这太愚蠢了。

以下代码再次失败:

class BusinessRepository implements Repository
{
    public function save(BusinessEntity $entity)
    {
      //this will fail, however BusinessEntity is an Entity
    }
}

2 个答案:

答案 0 :(得分:1)

通常,方法参数必须与继承层次结构或不变量相反。这意味着当用作方法参数的类型时,BusinessEntity确实与Entity“兼容”。

从“合同”的角度来考虑它。您的界面Repository 承诺其方法save可以处理Entity类型的参数。从Repository继承的子类型应该绑定到这个引入的契约(因为否则,如果你不能确定它们能够做什么,它首先定义类型会有什么意义?)。

现在,如果一个子类型突然只接受更多特殊类型,例如BusinessEntity,但不再是Entity,那么合同就会被破坏。您不能再将BusinessRepository用作Repository,因为您无法使用save致电Entity

这首先是违反直觉的,但请看一下:https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Contravariant_method_argument_type

注意图像中的继承箭头。

该怎么办?摆脱继承作为面向对象编程的圣杯的想法。大多数时候,它不是,并引入了各种令人讨厌的耦合。例如,支持组合而不是继承。看看Parameter type covariance in specializations

答案 1 :(得分:0)

它失败是因为您声明了在接口中使用不同参数的方法。还有一个问题是,在保存BusinessEntity时是否存在与Entity不同的逻辑。我认为不应该这样。因此,您可以省略业务实体中的保存功能,只保存实体上的工作,并且应该知道实体具有“保存”方法。

另一种方法是使用工厂模式或抽象工厂而不是继承。