Подход Дейва Хиллиера является правильным. Отдельный 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++. Трудно найти действительно хорошее объяснение, почему вы не должны. Обычно люди цитируют более эффективный C++ Мейерса 33-й предмет, который указывает на невозможность написания достойного operator=()
среди прочего. Вероятно, тогда вы должны никогда не делать это для классов со значениями семантики. Еще одна ошибка заключается в том, что конкретный класс не имеет виртуального деструктора (поэтому вы должны никогда публично не выходить из контейнеров STL). Здесь не применяется. Постер , который снисходительно послал вас Справку C++, чтобы узнать о наследовании является неправильно - добавление GetArea()
не нарушает Лиск взаимозаменяемости. О единственном риске, который я могу видеть, исходит из переопределения виртуальных функций в конкретных классах, когда реализатор позднее меняет имя и молча разбивает код.
В целом, я думаю, что вы можете получить от площади с чистой совестью. (В качестве утешения вам не нужно будет писать все функции переадресации для интерфейса формы ).
Теперь о проблемах функций, которым нужны оба интерфейса. Мне не нравится не нужен dynamic_cast
s.Вместо этого, сделать функцию принимают ссылки на обоих интерфейсов и передавать ссылки на тот же объект для обоих на месте вызова:
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
. Я бы опирался на здравый общий дизайн системы, чтобы исключить возможность битов двух разных экземпляров, подаваемых на такие функции.
Я склоняюсь к этому методу. Я мог бы добавить метод GetShape() в ImprovedShape, который скрывает приведение. Мне жаль, что не было времени для компиляции, чтобы проверить правильность. – Imbue 2008-10-30 13:58:47
Собственно, есть. Посмотрите на Boost.Type_traits - и вы должны увидеть признак производный_from <>. – 2008-10-31 07:25:05
В нашем случае нам не нужна была проверка времени компиляции. Это была библиотека управления графическим интерфейсом, поэтому было бы совершенно необычно, если бы кто-нибудь написал свой собственный контроль, не наследуя от существующего. Но да, это основная проблема этого метода. – Gorpik 2008-10-31 08:30:22