[PHP, Программирование, Совершенный код, Проектирование и рефакторинг, ООП] Принцип подстановки Барбары Лисков (предусловия и постусловия)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Почему у многих возникают проблемы с этим принципом? Если взять не «заумное», а более простое определение, то оно звучит так:
Наследующий класс должен дополнять, а не замещать поведение базового класса.
Звучит понятно и вполне логично, расходимся. но блин, как этого добиться? Почему-то многие просто пропускают информацию про предусловия и постусловия, которые как раз отлично объясняют что нужно делать.В данной статье мы НЕ будем рассматривать общие примеры данного принципа, о которых уже есть много материалов (пример с квадратом и прямоугольником или управления термостатами). Здесь мы немного подробнее остановимся на таких понятиях как «Предусловия», «Постусловия», рассмотрим что такое ковариантность, контравариантность и инвариантность, а также что такое «исторические ограничения» или «правило истории».Предусловия не могут быть усилены в подклассе️Другими словами дочерние классы не должны создавать больше предусловий, чем это определено в базовом классе, для выполнения некоторого бизнес-поведения. Вот пример:
<?php
class Customer
{
protected float $account = 0;
public function putMoneyIntoAccount(int|float $sum): void
{
if ($sum < 1) {
throw new Exception('Вы не можете положить на счёт меньше 1$');
}
$this->account += $sum;
}
}
class MicroCustomer extends Customer
{
public function putMoneyIntoAccount(int|float $sum): void
{
if ($sum < 1) {
throw new Exception('Вы не можете положить на счёт меньше 1$');
}
// Усиление предусловий
if ($sum > 100) {
throw new Exception('Вы не можете положить на больше 100$');
}
$this->account += $sum;
}
}
Добавление второго условия как раз является усилением. Так делать не надо!К предусловиям также следует отнести «Контравариантность», она касается параметров функции, которые может ожидать подкласс.
Подкласс может увеличить свой диапазон параметров, но он должен принять все параметры, которые принимает родительский.
Этот пример показывает, как расширение допускается, потому что метод Bar->process() принимает все типы параметров, которые принимает метод в родительском классе.
<?php
class Foo
{
public function process(int|float $value)
{
// some code
}
}
class Bar extends Foo
{
public function process(int|float|string $value)
{
// some code
}
}
Пример ниже показывает, как дочерний класс VIPCustomer может принимать в аргумент переопределяемого метода putMoneyIntoAccount более широкий (более абстрактный) объект Money, чем в его родительском методе (принимает Dollars).
<?php
class Money {}
class Dollars extends Money {}
class Customer
{
protected Money $account;
public function putMoneyIntoAccount(Dollars $sum): void
{
$this->account = $sum;
}
}
class VIPCustomer extends Customer
{
public function putMoneyIntoAccount(Money $sum): void
{
$this->account = $sum;
}
}
Таким образом, мы не добавляем дополнительных проверок, не делаем условия жестче и наш дочерний класс уже ведёт себя более предсказуемо. Постусловия не могут быть ослаблены в подклассе️То есть подклассы должны выполнять все постусловия, которые определены в базовом классе. Постусловия проверяют состояние возвращаемого объекта на выходе из функции.
<?php
class Customer
{
protected Dollars $account;
public function chargeMoney(Dollars $sum): float
{
$result = $this->account - $sum->getAmount();
if ($result < 0) { // Постусловие
throw new Exception();
}
return $result;
}
}
class VIPCustomer extends Customer
{
public function chargeMoney(Dollars $sum): float
{
$result = $this->account - $sum->getAmount();
if ($sum < 1000) { // Добавлено новое поведение
$result -= 5;
}
// Пропущено постусловие базового класса
return $result;
}
}
Условное выражение проверяющее результат является постусловием в базовом классе, а в наследнике его уже нет. Не делай так!Сюда-же можно отнести и «Ковариантность», которая позволяет объявлять в методе дочернего класса типом возвращаемого значения подтип того типа (ШО?!), который возвращает родительский метод. На примере будет проще. Здесь в методе render() дочернего класса, JpgImage объявлен типом возвращаемого значения, который в свою очередь является подтипом Image, который возвращает метод родительского класса Renderer.
<?php
class Image {}
class JpgImage extends Image {}
class Renderer
{
public function render(): Image
{
}
}
class PhotoRenderer extends Renderer
{
public function render(): JpgImage
{
}
}
️Таким образом в дочернем классе мы сузили возвращаемое значение. Не ослабили. Усилили :)ИнвариантностьЗдесь должно быть чуть проще.
Все условия базового класса - также должны быть сохранены и в подклассе.
Инварианты — это некоторые условия, которые остаются истинными на протяжении всей жизни объекта. Как правило, инварианты передают внутреннее состояние объекта.Например типы свойств базового класса не должны изменяться в дочернем.
<?php
class Wallet
{
protected float $amount;
// тип данного свойства не должен изменяться в подклассе
}
Здесь также стоит упомянуть исторические ограничения («правило истории»):
Подкласс не должен создавать новых мутаторов свойств базового класса.
Если базовый класс не предусматривал методов для изменения определенных в нем свойств, подтип этого класса так же не должен создавать таких методов. Иными словами, неизменяемые данные базового класса не должны быть изменяемыми в подклассе.
<?php
class Deposit
{
protected float $account = 0;
public function __construct(float $sum)
{
if ($sum < 0) {
throw new Exception('Сумма вклада не может быть меньше нуля');
}
$this->account += $sum;
}
}
class VipDeposit extends Deposit
{
public function getMoney(float $sum)
{
$this->account -= $sum;
}
}
С точки зрения класса Deposit поле не может быть меньше нуля. А вот производный класс VipDeposit, добавляет метод для изменения свойства account, поэтому инвариант класса Deposit нарушается. Такого поведения следует избегать. В таком случае стоит рассмотреть добавление мутатора в базовый класс.ВыводыДаже не вникая в общие сложные абстрктные примеры самого принципа подстановки Барбары Лисков, а пользуясь этими, на мой взгляд, более простыми и более конкретными правилами, вы уже добьётесь более предсказуемого поведения дочерних классов. Стоит упомянуть, что нужно страться избавляться от пред/пост условий. В идеале они должны быть определенны как входные/выходные параметры метода (например передачей в сигнатуру готовых value objects и возвращением конкретного валидного объекта на выход).Надеюсь, было полезно.Источники
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, Программирование, ReactJS] 7 лучших библиотек для создания молниеносно быстрых приложений ReactJS (перевод)
- [Программирование, Разработка игр, Unity, Игры и игровые приставки, Интервью] Подкаст «Хочу в геймдев» #25 — текстовая версия
- [Open source, Программирование, Отладка, Реверс-инжиниринг] Обратная отладка в большом масштабе (перевод)
- [Программирование микроконтроллеров, Производство и разработка электроники] HK32F030C8T6 全功能克隆(полный функциональный клон) STM32F030C8T6
- [Информационная безопасность, Программирование, C++] Грехи C++ (перевод)
- [Программирование, Работа с 3D-графикой, Алгоритмы, Визуализация данных] Майнкрафт для геологов: 3D-рендеринг миллиарда ячеек на встроенной видеокарте (часть 2)
- [Программирование, Машинное обучение] SberCloud + Intel oneAPI = бесплатное облако для ML-разработчиков
- [Open source, Программирование, C++] Развитие проекта arataga: пара рефакторингов по результатам натурных испытаний
- [Физика, Носимая электроника, Квантовые технологии] Обзор и тест RadiaCode-101: портативный дозиметр-радиометр-спектрометр
- [Веб-дизайн, Habr, JavaScript, Программирование, HTML] Косяк колхозной простоты в коде веб-сайта Хабра
Теги для поиска: #_php, #_programmirovanie (Программирование), #_sovershennyj_kod (Совершенный код), #_proektirovanie_i_refaktoring (Проектирование и рефакторинг), #_oop (ООП), #_oop (ооп), #_solid, #_liskov_substitution_principle, #_liskov, #_proektirovanie (проектирование), #_programmirovanie (программирование), #_kod (код), #_nasledovanie (наследование), #_printsip_podstanovki (принцип подстановки), #_printsip_podstanovki_barbary_liskov (принцип подстановки барбары лисков), #_php, #_programmirovanie (
Программирование
), #_sovershennyj_kod (
Совершенный код
), #_proektirovanie_i_refaktoring (
Проектирование и рефакторинг
), #_oop (
ООП
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:50
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Почему у многих возникают проблемы с этим принципом? Если взять не «заумное», а более простое определение, то оно звучит так: Наследующий класс должен дополнять, а не замещать поведение базового класса.
<?php
class Customer { protected float $account = 0; public function putMoneyIntoAccount(int|float $sum): void { if ($sum < 1) { throw new Exception('Вы не можете положить на счёт меньше 1$'); } $this->account += $sum; } } class MicroCustomer extends Customer { public function putMoneyIntoAccount(int|float $sum): void { if ($sum < 1) { throw new Exception('Вы не можете положить на счёт меньше 1$'); } // Усиление предусловий if ($sum > 100) { throw new Exception('Вы не можете положить на больше 100$'); } $this->account += $sum; } } Подкласс может увеличить свой диапазон параметров, но он должен принять все параметры, которые принимает родительский.
<?php
class Foo { public function process(int|float $value) { // some code } } class Bar extends Foo { public function process(int|float|string $value) { // some code } } <?php
class Money {} class Dollars extends Money {} class Customer { protected Money $account; public function putMoneyIntoAccount(Dollars $sum): void { $this->account = $sum; } } class VIPCustomer extends Customer { public function putMoneyIntoAccount(Money $sum): void { $this->account = $sum; } } <?php
class Customer { protected Dollars $account; public function chargeMoney(Dollars $sum): float { $result = $this->account - $sum->getAmount(); if ($result < 0) { // Постусловие throw new Exception(); } return $result; } } class VIPCustomer extends Customer { public function chargeMoney(Dollars $sum): float { $result = $this->account - $sum->getAmount(); if ($sum < 1000) { // Добавлено новое поведение $result -= 5; } // Пропущено постусловие базового класса return $result; } } <?php
class Image {} class JpgImage extends Image {} class Renderer { public function render(): Image { } } class PhotoRenderer extends Renderer { public function render(): JpgImage { } } Все условия базового класса - также должны быть сохранены и в подклассе.
<?php
class Wallet { protected float $amount; // тип данного свойства не должен изменяться в подклассе } Подкласс не должен создавать новых мутаторов свойств базового класса.
<?php
class Deposit { protected float $account = 0; public function __construct(float $sum) { if ($sum < 0) { throw new Exception('Сумма вклада не может быть меньше нуля'); } $this->account += $sum; } } class VipDeposit extends Deposit { public function getMoney(float $sum) { $this->account -= $sum; } } =========== Источник: habr.com =========== Похожие новости:
Программирование ), #_sovershennyj_kod ( Совершенный код ), #_proektirovanie_i_refaktoring ( Проектирование и рефакторинг ), #_oop ( ООП ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:50
Часовой пояс: UTC + 5