2015-07-27 1 views
2

У меня есть объекты и репозитории в моем проекте. Для упрощения, я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; 
    } 
} 

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

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

Следующий код не раз:

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

ответ

0

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

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

+0

Тем не менее смущен, потому что в SOLID, принцип замещения Лиск говорит > если S является подтипом T, тогда объекты типа T могут быть заменены на объекты типа S это точно то же самое, что я хочу сделать Вот. BusinessEntity является Entity, реализует Entity, я ожидаю Entity, и если BusinessEntity реализует Entity, почему я не могу использовать BusinessEntity как Entity ... – fureszpeter

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.