Я пишу фреймворк на PHP и столкнулся с шаблоном, который плохо пахнет. Похоже, что я реализую контракт (см. «Разработка по контракту»), который нарушает принцип замены Лискова (LSP). Поскольку исходный пример сильно абстрагирован, я помещу его в контекст реального мира:
(примечание: я не разбираюсь в двигателях/транспортных средствах/в-комнатах-в-комнатах, простите меня, если это нереально)
Предположим, у нас есть анемичный абстрактный класс для транспортных средств, и, кроме того, у нас есть два подтипа транспортных средств — те, которые можно заправлять, и те, которые нельзя (например, самокаты). В этом примере мы сосредоточимся только на перезаправляемом типе:
abstract class AbstractVehicle {}
abstract class AbstractFuelledVehicle extends AbstractVehicle
{
private $lastRefuelPrice;
final public function refuelVehicle(FuelInterface $fuel)
{
$this->checkFuelType($fuel);
$this->lastRefuelPrice = $fuel->getCostPerLitre;
}
abstract protected function checkFuelType(FuelInterface $fuel);
}
abstract class AbstractNonFuelledVehicle extends AbstractVehicle { /* ... */ }
Теперь давайте посмотрим на «топливные» классы:
abstract class AbstractFuel implements FuelInterface
{
private $costPerLitre;
final public function __construct($costPerLitre)
{
$this->costPerLitre = $costPerLitre;
}
final public function getCostPerLitre()
{
return $this->costPerLitre;
}
}
interface FuelInterface
{
public function getCostPerLitre();
}
Это все абстрактные классы, теперь давайте посмотрим на конкретные реализации. Во-первых, две конкретные реализации топлива, в том числе несколько анемичных интерфейсов, чтобы мы могли правильно набирать/обнюхивать их:
interface MotorVehicleFuelInterface {}
interface AviationFuelInterface {}
final class UnleadedPetrol extends AbstractFuel implements MotorVehicleFuelInterface {}
final class AvGas extends AbstractFuel implements AviationFuelInterface {}
Теперь, наконец, у нас есть конкретные реализации транспортных средств, которые обеспечивают использование правильного типа топлива (интерфейса) для заправки определенного класса транспортных средств, вызывая исключение, если оно несовместимо:
class Car extends AbstractFuelledVehicle
{
final protected function checkFuelType(FuelInterface $fuel)
{
if(!($fuel instanceof MotorVehicleFuelInterface))
{
throw new Exception('You can only refuel a car with motor vehicle fuel');
}
}
}
class Jet extends AbstractFuelledVehicle
{
final protected function checkFuelType(FuelInterface $fuel)
{
if(!($fuel instanceof AviationFuelInterface))
{
throw new Exception('You can only refuel a jet with aviation fuel');
}
}
}
Car и Jet являются подтипами AbstractFuelledVehicle, поэтому, согласно LSP, мы должны иметь возможность их заменить.
Из-за того, что checkFuelType() выдает исключение, если указан неверный подтип AbstractFuel, это означает, что если мы заменим Car на Jet (или наоборот) подтипа AbstractFuelledVehicle (или наоборот) без замены соответствующего топлива подтип, мы вызовем исключение.
Это:
- Определенное нарушение LSP, так как подстановка не должна вызывать изменения в поведении, приводящие к возникновению исключений.
- Это вообще не нарушение, так как все интерфейсы и абстрактные функции реализованы правильно и могут вызываться без нарушений типов.
- Немного серой зоны, чей ответ субъективен
AbstractFuelledVehicle::refuelVehicle()
звучит так: Заправляйте транспортное средство, если топливо совместимо. Он не сломан. - person zerkms   schedule 30.08.2016