Проблема с наследованием - PullRequest
1 голос
/ 29 августа 2010

Это фрагмент из пункта «Исключительный C ++», пункт 24, Решение, первая буква внизу страницы:

Никогда не используйте публичное наследование для реализации "IS-ALMOST-A". Я видел, как некоторые программисты, даже опытные, публично наследуют от базы и реализуют «большинство» переопределенных виртуальных функций таким образом, чтобы сохранить семантику базового класса. Другими словами, в некоторых случаях использование извлеченного объекта в качестве базы не будет вести себя так, как этого может ожидать разумный базовый клиент. Роберт Мартин часто приводит пример ошибочной идеи наследования класса Square от класса Rectangle, «потому что квадрат - это прямоугольник». Это может быть верно в математике, но это не обязательно верно в классах. Например, допустим, что класс Rectangle имеет виртуальную функцию SetWidth (int). Тогда реализация Square для установки ширины также естественным образом установит высоту, чтобы объект оставался квадратным. Тем не менее, в других местах системы может существовать код, который полиморфно работает с объектами Rectangle, и не ожидает, что изменение ширины также приведет к изменению высоты. В конце концов, это не относится к прямоугольникам в целом! Это хороший пример открытого наследования, которое нарушает LSP, потому что производный класс не предоставляет ту же семантику, что и базовый класс. Это нарушает ключевой принцип общественного наследования: «Не требуй больше и не обещай не меньше».

Я попытался проверить это и написал:

// Square.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
using namespace std;
class Rectangle
{
private:
 unsigned width_;
 unsigned height_;
public:
 Rectangle(const unsigned width, const unsigned height):width_(width),height_(height)
 {/*Empty body*/ }
 unsigned GetWidth()const
 {
  return width_;
 }
 unsigned GetHeight()const
 {
  return height_;
 }
 virtual void SetWidth(const unsigned width)
 {
  width_ = width;
 }
 void SetHeight(const unsigned height)
 {
  height_ = height;
 }
 virtual ~Rectangle()
 {
  cout << "~Rectangle()" << '\n';
 };
};

class Square : public Rectangle
{
 using Rectangle::SetWidth;
public:
 Square(const unsigned width):Rectangle(width,width)
 {
 }
 void SetWidth(const unsigned width)
 {

  SetWidth(width);
  SetHeight(width);
 }
 ~Square()
 {
  cout << "~Sqare()" << '\n';
 }
};

int _tmain(int argc, _TCHAR* argv[])
{
 Rectangle** a = static_cast<Rectangle**>(operator new (sizeof(Rectangle) * 2));
 a[0] = new Rectangle(10,10);
 a[1] = new Square(5);
 Rectangle* r = a[0];
 cout << r->GetHeight() << "\t" << r->GetWidth() << '\n';
 r = a[1];
 cout << r->GetHeight() << "\t" << r->GetWidth() << '\n';
 r = a[0];
 r->SetWidth(20);//here I'm setting just width for a Rectangle
 cout << r->GetHeight() << "\t" << r->GetWidth() << '\n';
delete a[1];
delete a;
     return 0;
    }

Что касается меня, наследование Square от Rectangle работает так, как задумано. Так где же я ошибаюсь и не понимаю, что сказано в этой пуле?
Спасибо

Ответы [ 2 ]

5 голосов
/ 29 августа 2010

Дело в том, что семантика для класса Square отличается от семантики класса Rectangle.Скажем, у вас есть общая вспомогательная функция, подобная этой:

void doubleArea(Rectangle &rect) {
  rect.setWidth(rect.getWidth() * 2);
}

Предполагается, что вызов этой функции удвоит площадь (ширину × высоту) данного прямоугольника.

С вашимПроизводный класс Square, теперь вы можете сделать что-то вроде этого:

Square sq(1);
doubleArea(sq);

Внезапно sq имеет четыре раза область, затем перед вызовом.Вы только хотели удвоить площадь, но получили неверный результат.

Поскольку Square не имеет точно такой же семантики, что и Rectangle, иногда выполнение действий, подходящих для прямоугольников, не будет работать для квадратов,Поэтому не стоит утверждать, что Square - это Rectangle, получая одно из другого, поскольку производный класс не может выполнить все требования / обещания, сделанные базовым классом.

Дляподробнее об этом также читайте в статье C ++ FAQ Lite «Является ли круг чем-то вроде эллипса?» .

0 голосов
/ 29 августа 2010

Ваш код в порядке. Пуля предполагает, что кто-то может написать некоторый код, который зависит от высоты прямоугольника, оставшегося неизменным при вызове SetWidth:

int old_height = r->GetHeight();
r->SetWidth(100);
assert old_height == r->GetHeight();

Этот код не будет работать с вашей реализацией SetWidth в Square.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...