【问题标题】:PHP Interfaces and argument inheritancePHP 接口和参数继承
【发布时间】:2015-10-17 21:13:59
【问题描述】:

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

  • 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 实现了 Entity,所以它是兼容的。

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

以下代码再次失败:

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

【问题讨论】:

    标签: php interface extend


    【解决方案1】:

    一般来说,方法参数必须相对于继承层次结构是逆变的或不变量。这意味着 BusinessEntity 在用作方法参数的类型时确实与 Entity“兼容”。

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

    现在,如果一个子类型突然只接受更多特殊类型,例如BusinessEntity,但不再接受Entity,则合同已失效。您不能再将BusinessRepository 用作Repository,因为您不能使用Entity 调用save

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

    注意图中的继承箭头。

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 仍然很困惑,因为在 SOLID 中,Liskov 替换原则说 >如果 S 是 T 的子类型,那么 T 类型的对象可能会被 S 类型的对象替换这与我想要的完全一样在这里做。 BusinessEntity是Entity,实现Entity,我期待Entity,如果BusinessEntity实现Entity,为什么不能用BusinessEntity作为Entity...
      猜你喜欢
      • 1970-01-01
      • 2010-09-21
      • 1970-01-01
      • 1970-01-01
      • 2011-07-31
      • 2011-10-05
      • 2013-02-19
      相关资源
      最近更新 更多