Это гостевой выпуск Пятиминутки PHP — ведёт Кирилл Сулимовский.
Также порекомендую подписаться на телеграм канал Кирилла: https://t.me/beerphp
Принцип подстановки Барбары Лисков
❓Почему у многих возникают проблемы с этим принципом? Если взять не заумное , а более простое объяснение, то оно звучит так:
«Наследующий класс должен дополнять, а не замещать поведение базового класса».
Звучит логично и понятно, расходимся. но блин, как этого добиться? Почему-то многие просто пропускают мимо ушей следующие строки, которые как раз отлично объясняют что нужно делать.
Рассмотрим выражение «Предусловия не могут быть усилены в подклассе»
❗️Другими словами дочерние классы не должны создавать больше предусловий, чем это определено в базовом классе, для выполнения некоторого бизнесового поведения. Вот несложный пример.
❌ Добавление второго условия как раз является усилением. Так делать не надо!
Контравариантность также можно отнести к данному приниципу. Она касается параметров функции, которые может ожидать подкласс. Подкласс может увеличить свой диапазон параметров, но он должен принять все параметры, которые принимает родительский.
• Этот пример показывает, как расширение допускается, потому что метод Bar->process()
принимает все типы параметров, которые принимает родительский метод.
• В этом примере дочерний класс может принимать более широкий спектр объектов, которые могут быть наследованы от Money
, не только Dollars
.
Таким образом, мы не добавляем дополнительных проверок, не делаем условия жестче и наш дочерний класс уже ведёт себя более предсказуемо.
В следующих постах мы также рассмотрим постуловия и ковариантность ;)
«Постусловия не могут быть ослаблены в подклассе».
То есть подклассы должны выполнять все постусловия, которые определены в базовом классе. Постусловия проверяют состояние возвращаемого объекта на выходе из функции. Вот такой пример.
❌Условное выражение проверяющее результат является постусловием в базовом классе, а в наследнике его уже нет. Не делай так!
Сюда-же можно отнести и ковариантность, которая позволяет объявлять в методе дочернего класса типом возвращаемого значения подтип того типа (ШО?!), который возвращает родительский метод.
Короче, в данном примере, в методе render()
дочернего класса, JpgImage
объявлен типом возвращаемого значения, который в свою очередь является подтипом Image
, который возвращает метод родительского класса Renderer
.
❗️Таким образом в дочернем классе мы сузили возвращаемое значение. Не ослабили. А усилили :)
❗️Все условия базового класса — также должны быть сохранены и в подклассе.
Инварианты — это некоторые условия, которые остаются истинными на протяжении всей жизни объекта. Как правило, инварианты передают внутреннее состояние объекта. Например типы свойств базового класса не должны изменяться в дочернем.
class Wallet
{
protected float $amount;
// тип данного свойства не должен изменяться в подклассе
}
Здесь также стоит упомянуть исторические ограничения («правило истории»):
❗️Подкласс не должен создавать новых мутаторов свойств базового класса.
Если базовый класс не предусматривал методов для изменения определенных в нем свойств, подтип этого класса так же не должен создавать таких методов. Иными словами, неизменяемые данные базового класса не должны быть изменяемыми в подклассе.
❌ С точки зрения класса Deposit
поле не может быть меньше нуля. А вот производный класс VipDeposit
, добавляет метод для изменения свойства account
, поэтому инвариант класса Deposit
нарушается. Такого поведения следует избегать. В таком случае стоит рассмотреть добавление мутатора в базовый класс.