Интерфейсы PHP и наследование аргументов

У меня есть объекты и репозитории в моем проекте. Чтобы упростить, у меня есть

  • 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;
}
}

Я хотел бы определить базовую функциональность репозитория, как 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 реализует Entity, поэтому он совместим.

У меня много типов сущностей, поэтому, если я не могу напечатать подсказку, мне всегда нужно проверить, что переданный экземпляр является экземпляром того, что мне нужно. Это глупо.

Следующий код снова не работает:

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

2

Решение

В общем, параметры метода должны быть противоположными по отношению к иерархии наследования или инвариантными. Это означает, что 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

Обратите внимание на стрелку наследования на изображении.

Что делать? Избавьтесь от идеи наследования как святого Грааля в объектно-ориентированном программировании. В большинстве случаев это не так, и вводит все виды неприятных связей. Например, композицию, отдавшую предпочтение наследованию. Посмотри на Тип параметра ковариация в специализациях.

1

Другие решения

Это терпит неудачу, потому что вы объявляете методы, которые принимают разные аргументы в интерфейсах. Также возникает вопрос, существует ли какая-либо иная логика в сохранении BusinessEntity, чем Entity. Я думаю, что это не должно быть. Таким образом, вы можете опустить функцию сохранения в бизнес-объекте и сохранить только работу на Entity и должны знать, что Entity имеет метод «save».

Другой способ — использовать фабричный шаблон или абстрактную фабрику вместо наследования.

0

По вопросам рекламы [email protected]