>Получается, что нельзя, к примеру, определить для человека и камина общий базовый класс «Штука с температурой»...
Нельзя. Для базового класса «Штука с температурой» не определено поведение, на которое будут рассчитывать его пользователи. Можно было бы определить поведение(предусловие) класса «Штука с температурой» несколькими способами:
1) принимаем любую температуру
Но тогда, т.к. Firepace и Human задают более жесткие предусловия, они не могут быть его подтипами.
2) принимает температуру < 800 градусов
В этом случае только класс Fireplace мог бы быть подтипом, т.к. предусловия Fireplace не являются более жесткими, чем у «Штуки»
3) принимает температуру < 45 градусов
только Human может быть подтипом
>. Если попробовать сделать наоборот… не должен ли тогда камин соблюдать предусловия человека?
В моём исходнике я не снабдил контрактом функцию setTemperature. Если снабдить, то станет видно, что «наоборот» наследовать классы нельзя.
(см. комментарии внутри функции Fireplace::setTemperature)
#include <iostream>
#include <cassert>
class Human {
public:
Human() { setTemperature(36); }
// функция-гетер
virtual int getTemperature() const {
return temperature;
}
// функция-сетер
virtual void setTemperature(int N) {
// preconditions
int old_temperature = getTemperature();
// возможный диапазон значений температуры для человека
assert(N >= 36 && N < 45);
temperature = N;
// postconditions
assert(getTemperature() == N);
// гарантируем, что температура человека будет лежать в этих пределах
assert(getTemperature() >= 36 && getTemperature() < 45);
}
virtual void heat(int N) {
// preconditions
int old_temperature = getTemperature();
// N должно быть такое, чтобы человек не перегрелся
assert(getTemperature() + N < 45);
// postconditions
assert(getTemperature() == old_temperature + N);
// гарантируем, что температура человека находится в допустимых пределах
assert(getTemperature() >= 36 && getTemperature() < 45);
}
protected:
int temperature;
};
//-----------------------------------------------------------------------------
class Fireplace : public Human {
public:
// средняя температура камина без дров (т.е. кирпича внутри него) 18 градусов
Fireplace() { setTemperature(18); }
// функция-сетер
virtual void setTemperature(int N) {
// preconditions
int old_temperature = getTemperature();
// возможный диапазон значений температуры для человека был такой:
// assert(N >= 36 && N < 45);
//новый assert для камина примет вид:
assert(N > 0 && N < 800);
temperature = N;
// postconditions
assert(getTemperature() == N);
// для человека было такое постусловие:
// assert(getTemperature() >= 36 && getTemperature() < 45);
// но для камина у нас должно быть другое:
// assert(getTemperature() > 0 && getTemperature() < 800);
// однако мы не имеем право его использовать, т.к.
// getTemperature() > 0 - ОСЛАБЛЯЕТ постусловие базового класса
// следовательно, или необходимо изменить постусловие базового класса
// или, на данный момент, класс Fireplace не может быть подтипом Human
}
// подогреть на N градусов
virtual void heat(int N) {
// preconditions
int old_temperature = getTemperature();
// кирпич в камине выдерживает максимум 800 градусов Цельсия
assert(getTemperature() + N < 800);
Согласен. Однако поддержка на С++ есть в виде сторонних библиотек. Достаточно погуглить по «С++ Design by Contract».
Например эта: sourceforge.net/projects/dbcpp/
Основная проблема — это то, что (насколько я понял) предусловия/постусловия и их ослабления/усиления не контролируются компилятором, следовательно программисты должны сами следить за контрактами, что неизбежно приведёт к их(контрактов) непониманию и несоблюдению. Хотя методология эта хороша уже тем, что заставляет задуматься о границах применимости класса, его контрактах с другими классами и т.д.
>предпочитаю дедовские способы — если человек является камином — то может быть подклассом, иначе нет.
Предположим, что вместо названия класса «Человек» было бы другое: «Пластилиновый камин».
Всё остальное в программе оставим прежним. В этом случае Вы бы могли посчитать его подклассом класса «Камин».
Хотя «пластилиновые камины» не ведут себя поведенчески также, как и обычные камины(они плавятся при 45 градусах). Следовательно, по LSP, они не могут быть взаимозаменяемыми сущностями и не подходят под отношение тип-подтип.
Нельзя. Для базового класса «Штука с температурой» не определено поведение, на которое будут рассчитывать его пользователи. Можно было бы определить поведение(предусловие) класса «Штука с температурой» несколькими способами:
1) принимаем любую температуру
Но тогда, т.к. Firepace и Human задают более жесткие предусловия, они не могут быть его подтипами.
2) принимает температуру < 800 градусов
В этом случае только класс Fireplace мог бы быть подтипом, т.к. предусловия Fireplace не являются более жесткими, чем у «Штуки»
3) принимает температуру < 45 градусов
только Human может быть подтипом
>. Если попробовать сделать наоборот… не должен ли тогда камин соблюдать предусловия человека?
В моём исходнике я не снабдил контрактом функцию setTemperature. Если снабдить, то станет видно, что «наоборот» наследовать классы нельзя.
(см. комментарии внутри функции Fireplace::setTemperature)
Например эта: sourceforge.net/projects/dbcpp/
Основная проблема — это то, что (насколько я понял) предусловия/постусловия и их ослабления/усиления не контролируются компилятором, следовательно программисты должны сами следить за контрактами, что неизбежно приведёт к их(контрактов) непониманию и несоблюдению. Хотя методология эта хороша уже тем, что заставляет задуматься о границах применимости класса, его контрактах с другими классами и т.д.
Предположим, что вместо названия класса «Человек» было бы другое: «Пластилиновый камин».
Всё остальное в программе оставим прежним. В этом случае Вы бы могли посчитать его подклассом класса «Камин».
Хотя «пластилиновые камины» не ведут себя поведенчески также, как и обычные камины(они плавятся при 45 градусах). Следовательно, по LSP, они не могут быть взаимозаменяемыми сущностями и не подходят под отношение тип-подтип.