«Принцип подстановки Лисков(LSP)» и «Проектирование по контракту (DbC)»- хорошо объясняется в этой англоязычной PDF
Объясню в 2 словах, что это такое:
DbC: функции снабжаются предусловиями(preconditions) и постусловиями(postconditions). Своё выполнение функция начинает только, если предусловия удовлетворены. После своего завершения фукнция гарантирует, что постусловия и инварианты класса будут соблюдены/непротиворечивы.
LSP — это один из способов определения того, может ли быть один класс подтипом другого. Основывается на отношении IS-A. Только не в плане внешнего сходства классов, а в плане их поведенческой заменяемости при использовании ссылки на базовый класс объекта производного класса.
На примере кода покажу, почему нельзя класс Человек(Human) наследовать от класса Камин(Fireplace).
Кстати, если бы камин мог нагреваться только до 45 градусов — то он бы мог с точки зрения LSP быть подтипом Fireplace :)
Буду рад, если статья кому-нибудь помогла, немножко прояснила эти принципы.
Объясню в 2 словах, что это такое:
DbC: функции снабжаются предусловиями(preconditions) и постусловиями(postconditions). Своё выполнение функция начинает только, если предусловия удовлетворены. После своего завершения фукнция гарантирует, что постусловия и инварианты класса будут соблюдены/непротиворечивы.
LSP — это один из способов определения того, может ли быть один класс подтипом другого. Основывается на отношении IS-A. Только не в плане внешнего сходства классов, а в плане их поведенческой заменяемости при использовании ссылки на базовый класс объекта производного класса.
На примере кода покажу, почему нельзя класс Человек(Human) наследовать от класса Камин(Fireplace).
#include <iostream>
#include <cassert>
class Fireplace {
public:
// средняя температура камина без дров (т.е. кирпича внутри него) 18 градусов
Fireplace() : temperature(18) { }
// функция-гетер
int getTemperature() const {
return temperature;
}
// функция-сетер
void setTemperature(int N) {
temperature = N;
}
// подогреть на N градусов
virtual void heat(int N) {
// preconditions
int old_temperature = getTemperature();
// кирпич в камине выдерживает максимум 800 градусов Цельсия
assert(getTemperature() + N < 800);
std::cout << "Подбросали дровишек";
setTemperature(getTemperature() + N);
// postconditions
// новая температура должна быть равна старой + N
assert(getTemperature() == old_temperature + N);
}
private:
int temperature;
};
class Human : public Fireplace {
public:
Human() { setTemperature(36); }
virtual void heat(int N) {
// preconditions
int old_temperature = getTemperature();
assert(getTemperature() + N < 800); // этот assert не имеет смысла, т.к.
// 800 градусов - многовато для человека
// человек максимум может нагреться на 45 градусов
// но мы не имеем права определить такой assert:
// assert(getTemperature() + N < 45);
// иначе мы бы УСИЛИЛИ предусловие и произошло бы
// нарушение контракта базового класса!
// Правила DbC:
// 1) предусловия в производных классах можно только ослаблять, но не усиливать
// 2) Постусловия можно усиливать, но не ослаблять
// следовательно - класс Human не может быть подтипом Fireplace
// т.к. помимо нарушется контракта нарушается сам принцип LSP,
// согласно которому объекты базовых/произвдных должны быть
// поведенчески эквивалентными и взаимозаменяемыми
std::cout << "Выпил водки";
setTemperature(getTemperature() + N);
// postconditions базового класса. не изменяем ничего в них.
// новая температура должна быть равна старой + N
assert(getTemperature() == old_temperature + N);
}
};
int main()
{
Fireplace *f = new Human;
f->heat(600);
return 0;
}
* This source code was highlighted with Source Code Highlighter.
Кстати, если бы камин мог нагреваться только до 45 градусов — то он бы мог с точки зрения LSP быть подтипом Fireplace :)
Буду рад, если статья кому-нибудь помогла, немножко прояснила эти принципы.