我的项目中有实体和存储库。为了简化,我有
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);
}
稍后,我为不同类型的存储库提供了不同的界面,例如UserRepository
和BusinessRepository
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
}
}
答案 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不同的逻辑。我认为不应该这样。因此,您可以省略业务实体中的保存功能,只保存实体上的工作,并且应该知道实体具有“保存”方法。
另一种方法是使用工厂模式或抽象工厂而不是继承。