Подход Дейва Хиллиера является правильным. Разделите GetArea()
на собственный интерфейс:
class ThingWithArea
{
public:
virtual double GetArea() const = 0;
};
Если дизайнеры Shape правильно сделали и сделали его чистым интерфейсом,
и публичные интерфейсы конкретных классов были достаточно мощными, вы могли бы
есть экземпляры конкретных классов в качестве членов. Вот как вы получаете SquareWithArea
(ImprovedSquare
- плохое имя), будучи Shape
и ThingWithArea
:
class SquareWithArea : public Shape, public ThingWithArea
{
public:
double GetPerimeter() const { return square.GetPerimeter(); }
double GetArea() const { /* do stuff with square */ }
private:
Square square;
};
К сожалению, Shape
разработчики вложили некоторую реализацию в Shape
, и вы
в конечном итоге несет две копии этого в SquareWithArea
, как в
алмаз, который вы изначально предложили.
Это в значительной степени вынуждает вас в наиболее тесно связанных, и, следовательно, наименее
желательно, решение:
class SquareWithArea : public Square, public ThingWithArea
{
};
В наши дни считается плохой формой наследования конкретных классов в C ++.
Трудно найти действительно хорошее объяснение, почему вы не должны. Обычно люди
процитировать более эффективный пункт 33 Мейерса C ++, который указывает на невозможность
написания приличного operator=()
среди прочего. Вероятно, тогда вы должны
никогда не делайте это для классов с семантикой значения. Еще одна ловушка, где
конкретный класс не имеет виртуального деструктора (вот почему вы должны
никогда публично не наследовать от контейнеров STL). Ни то, ни другое здесь не применимо. Плакат
кто снисходительно послал вас в C ++ часто задаваемые вопросы, чтобы узнать о наследовании
неправильно - добавление GetArea()
не нарушает подставляемость Лискова. Около
единственный риск, который я вижу, связан с переопределением виртуальных функций в
конкретные классы, когда разработчик позже меняет имя и молча ломает
ваш код.
Подводя итог, я думаю, что вы можете сделать вывод из Квадрата с чистой совестью.
(В качестве утешения вам не придется писать все функции пересылки для
интерфейс Shape).
Теперь о проблеме функций, которым нужны оба интерфейса. Мне не нравится
ненужные dynamic_cast
с. Вместо этого заставьте функцию принимать ссылки на
оба интерфейса и передают ссылки на один и тот же объект для обоих на сайте вызова:
void PrintPerimeterAndArea(const Shape& s, const ThingWithArea& a)
{
cout << s.GetPerimeter() << endl;
cout << a.GetArea() << endl;
}
// ...
SquareWithArea swa;
PrintPerimeterAndArea(swa, swa);
Все, что нужно PrintPerimeterAndArea()
для выполнения своей работы - это источник периметра и
Источник области. Это не его забота о том, чтобы это произошло
как функции-члены на одном экземпляре объекта. Возможно, область могла
быть снабжен каким-то механизмом численного интегрирования между ним и Shape
.
Это приводит нас к единственному случаю, когда я рассмотрел бы передачу в одной ссылке
и получить другой dynamic_cast
- где важно, чтобы два
ссылки на один и тот же экземпляр объекта. Вот очень надуманный пример:
void hardcopy(const Shape& s, const ThingWithArea& a)
{
Printer p;
if (p.HasEnoughInk(a.GetArea()))
{
s.print(p);
}
}
Даже тогда я бы предпочел послать две ссылки, а не
dynamic_cast
. Я бы положился на здравый общий дизайн системы, чтобы устранить
возможность использования битов двух разных экземпляров для таких функций.