Ответ 1
Ваш код является явным нарушением принципа замены Лискова. Для абстрактного класса требуется, чтобы экземпляр O_Base
передавался методу save
, поэтому все дочерние элементы A_Abstract
должны быть определены таким образом, чтобы они могли принимать все экземпляры O_Base
. Ваш дочерний класс реализует версию save
, которая дополнительно ограничивает API. см. другой пример здесь
В вашем коде A
нарушается контракт, который Abstract_A
устанавливает/описывает. Как и в реальной жизни, если вы подписываете контракт, вы должны согласиться с определенными условиями, и все согласны с терминологией, которую вы собираетесь использовать. Вот почему большинство контрактов начинается с названия сторон, а затем говорит что-то вроде "Отныне г-н Х будет называться сотрудником".
Эти термины, как и ваши абстрактные намеки типа, не подлежат обсуждению, так что в дальнейшем по линии вы не можете сказать: "О, ну... что вы называете расходами, я называю стандартную заработную плату"
Хорошо, я перестану использовать эти аналогии с половиной арсеналом и просто использую простой пример, чтобы проиллюстрировать, почему то, что вы делаете, по праву не допускается.
Рассмотрим это:
abstract class Foo
{
abstract public function save(Guaranteed $obj);
//or worse still:
final public function doStuff(Guaranteed $obj)
{
$obj->setSomething('to string');
return $this->save($obj);//<====!!!!
}
}
class FBar extends Foo
{
public function save(Guaranteed $obj)
{
return $obj->setFine(true);
}
}
class FBar2 extends Foo
{
public function save(ChildOfGuaranteed $obj)
{//FAIL: This method is required by Foo::doStuff to accept ALL instances of Guaranteed
}
}
См. здесь, в этом случае вполне допустимый абстрактный класс вызывает метод save
с экземпляром Guaranteed
. Если вам разрешено вводить более строгий тип-подсказку в дочерних элементах этого класса, вы можете легко разбить этот метод doStuff
. Чтобы помочь вам защитить себя от этих видов ран, нанесенных самим себе, детям не следует применять более строгие типы методов, которые они наследуют от своих родительских классов.
Также рассмотрим сценарий, в котором мы зацикливаемся над некоторыми экземплярами и проверяем, есть ли у них этот метод save
, на основе этих экземпляров: instanceof Foo
:
$arg = new OtherChildOfGuaranteed;
$array = array(
'fb' => new FBar,
'fb2' => new FBar2
);
foreach($array as $k => $class)
{
if ($class instanceof Foo) $class->save($arg);
}
Теперь это будет нормально работать, если вы просто намекаете на Guaranteed
в сигнатуре метода. Но во втором случае мы немного изменили тип-подсказку, и этот код приведет к фатальной ошибке. Получите удовольствие отладки этого в более сложном проекте...
PHP очень прощает, если не слишком прощает в большинстве случаев, но не здесь. Вместо того, чтобы вы почесывали голову, пока ваш слух не выпадет, PHP очень разумно дает вам голову, говоря, что реализация вашего метода нарушает контракт, поэтому вы должны исправить эту проблему.
Теперь быстрый способ обхода (и это тоже широко используется):
class FBar2 extends Foo
{
/**
* FBar2 save implementation requires instance of ChildOfGuaranteed
* Signature enforced by Foo
* @return Fbar2
* @throw InvalidArgumentException
**/
public function save(Guaranteed $obj)
{
if (!$obj instanceof ChildOfGuaranteed)
throw new InvalidArgumentException(__METHOD__.' Expects instance of ChildOfGuaranteed, you passed '.get_class($obj));
//do save here...
}
}
Итак, вы просто оставляете абстрактный тип-подсказку как есть, но используйте докблоки для документирования вашего кода