Я пишу фреймворк в PHP и сталкивался с паттерном, который плохо пахнет. Похоже, что я выполняю контракт (q.v. Design By Contract), который нарушает принцип Liskov Substituion (LSP). Так как исходный пример сильно отведенный, я положу его в реальный мире контекст:Принцип замены Лискова (LSP) нарушен через проект по контракту (DBC)?
(пь Я не двигатель/транспортное средство/Врум-Врум людей, простите меня, если это нереально)
Предположим, у нас есть анемичный абстрактный класс для транспортных средств, и у нас есть еще два подтипа транспортных средств - те, которые могут быть заправлены топливом, и те, которые не могут (например, pushbikes). Для этого примера, мы сосредоточимся только на refuellable типа:
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');
}
}
}
автомобилей и Jet оба подтипа AbstractFuelledVehicle, поэтому в соответствии с LSP, мы должны быть в состоянии заменить их.
Из-за того, что checkFuelType() выбрасывает исключение, если указан неправильный подтип AbstractFuel, это означает, что если мы заменим подтип AbstractFuelledVehicle для Jet (или наоборот) , не заменив также соответствующий подтип топлива, мы инициируем исключение.
Является ли это:
- Определенное нарушение LSP, как замена не должна вызывать изменения в поведении, что приводит к исключениям бросают
- Не нарушение вообще, как интерфейсы и абстрактные функции реализованы с правильно и по-прежнему можно назвать без нарушений типа
- немного серой области, чей ответ является субъективным
Я не думаю, что это нарушение. – zerkms
Я тоже так не думаю, но я хотел бы посмотреть, что думает сообщество. Там могут быть какие-то сторонние meeseeks :) –
В этом случае договор на 'AbstractFuelledVehicle :: refuelVehicle()' есть: «Заправьте автомобиль, если топливо совместимо». Это не сломано. – zerkms