Как стать автором
Обновить

[C++]Проектирование по контракту и принцип LSP на примере «Почему класс Человек не может быть подтипом класса Камин»

Время на прочтение3 мин
Количество просмотров1.2K
«Принцип подстановки Лисков(LSP)» и «Проектирование по контракту (DbC)»- хорошо объясняется в этой англоязычной PDF

Объясню в 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 :)
Буду рад, если статья кому-нибудь помогла, немножко прояснила эти принципы.
Теги:
Хабы:
+8
Комментарии9

Публикации