Вывод абстрактного класса из конкретного класса
Скажем, у нас есть конкретный class Apple
. (Объекты Apple могут быть созданы).
Теперь кто-то приходит и получает абстрактную class Peach
от Apple. Он абстрактный, потому что он вводит новую чистую виртуальную функцию. Теперь пользователь Персика вынужден извлечь из него и определить эту новую функцию. Это общий шаблон? Правильно ли это делать?
Пример:
class Apple
{
public:
virtual void MakePie();
// more stuff here
};
class Peach : public Apple
{
public:
virtual void MakeDeliciousDesserts() = 0;
// more stuff here
};
Теперь скажем, что мы имеем конкретный
class Berry
. Кто-то получает абстрактную
class Tomato
от Berry. Он абстрактный, потому что он перезаписывает одну из виртуальных функций Берри и делает ее чистой виртуальной. Пользователь Tomato должен повторно реализовать функцию, определенную ранее в Berry. Это общий шаблон? Правильно ли это делать?
Пример:
class Berry
{
public:
virtual void EatYummyPie();
// more stuff here
};
class Tomato : public Berry
{
public:
virtual void EatYummyPie() = 0;
// more stuff here
};
Примечание: имена надуманны и не отражают никакого реального кода (надеюсь). Никакие плоды не пострадали при написании этого вопроса.
Ответы
Ответ 1
Re Peach от Apple:
- Не делайте этого, если Apple - класс значений
(то есть имеет копию ctor, не идентичную
экземпляры могут быть равными и т.д.). Смотрите Meyers
Более эффективный С++. Пункт 33 для чего.
- Не делайте этого, если у Apple есть общественность
невиртуальный деструктор, иначе вы
пригласите undefined поведение, когда
пользователи удаляют Apple через
указатель на Персик.
- В противном случае вы, вероятно, будете в безопасности, потому что вы не нарушили Liskov substitutability. Peach IS-A Apple.
- Если у вас есть код Apple, предпочтите учитывать общий абстрактный базовый класс (возможно, "Фрут" ) и получить от него Apple и Peach.
Re Tomato из Berry:
- То же, что и выше, плюс:
- Избегайте, потому что это необычно
- Если вы должны, документируйте, какие производные классы Томатов должны сделать, чтобы не нарушать замену Лискова. Функция, которую вы переопределяете в Berry - позвоните ей
Juice()
- накладывает определенные требования и делает определенные promises. Реализации производных классов Juice()
не должны требовать больше и обещать не меньше. Тогда Berry DerivedTomato IS-A Berry и код, который знает только о Berry, безопасны.
Возможно, вы встретите последнее требование, зарегистрировав, что DerivedTomatoes должен вызвать Berry::Juice()
. Если да, рассмотрите вместо этого метод шаблона:
class Tomato : public Berry
{
public:
void Juice()
{
PrepareJuice();
Berry::Juice();
}
virtual void PrepareJuice() = 0;
};
Теперь есть отличный шанс, что Tomato IS-A Berry, вопреки ботаническим ожиданиям. (Исключение состоит в том, что реализации производных классов PrepareJuice
налагают дополнительные предпосылки за пределы, налагаемые Berry::Juice
).
Ответ 2
Мне кажется, что это показатель плохого дизайна. Может быть принудительно, если вы хотите взять конкретное определение из закрытой библиотеки и расширить его и отделить от него кучу вещей, но в этот момент я буду серьезно рассматривать руководство по инкапсуляции над наследством. Если вы, возможно, можете инкапсулировать, вы, вероятно, должны.
Да, чем больше я думаю об этом, это очень плохая идея.
Ответ 3
Не обязательно неправильно, но определенно вонючий. Особенно, если вы слишком долго оставляете фрукты на солнце. (И я не думаю, что мой дантист хотел бы, чтобы я ест конкретные яблоки.)
Хотя, главное, что я вижу здесь, что вонючий - это не столько абстрактный класс, который был получен из конкретного класса, но и иерархия наследования DEALY DEEP.
EDIT: повторное чтение Я вижу, что это две иерархии. Все плодовые вещи меня перепутали.
Ответ 4
Если вы используете рекомендуемую практику наличия модели наследования "is-a", тогда этот шаблон почти никогда не появлялся.
Как только у вас есть конкретный класс, вы говорите, что это то, на что вы действительно можете создать экземпляр. Если затем вывести из него абстрактный класс, то что-то, что является атрибутом базового класса, не относится к производному классу, который должен установить klaxons, что-то не так.
Глядя на ваш пример, персик не яблоко, поэтому он не должен быть получен из него. То же самое верно для Томатов, происходящих от Берри.
Вот где я обычно советую сдерживание, но это даже не кажется хорошей моделью, поскольку Apple не содержит персика.
В этом случае я бы отказался от общего интерфейса - PieFilling или DessertItem.
Ответ 5
немного необычный, но если у вас был какой-то другой подкласс базового класса, а подклассы абстрактного класса имели достаточно общий материал, чтобы оправдать существование абстрактного класса, например:
class Concrete
{
public:
virtual void eat() {}
};
class Sub::public Concrete { // some concrete subclass
virtual void eat() {}
};
class Abstract:public Concrete // abstract subclass
{
public:
virtual void eat()=0;
// and some stuff common to Sub1 and Sub2
};
class Sub1:public Abstract {
void eat() {}
};
class Sub2:public Abstract {
void eat() {}
};
int main() {
Concrete *sub1=new Sub1(),*sub2=new Sub2();
sub1->eat();
sub2->eat();
return 0;
}
Ответ 6
Хм... подумав "что за....." на пару секунд, я пришел к выводу, что это не распространено...
Кроме того, я бы не получил Персик от Apple и Tomato от Berry... у вас есть лучший пример?:)
Это много странного дерьма, которое вы можете сделать на С++... Я даже не могу думать о 1% этого...
О переопределении виртуального с чистым виртуальным, вы можете просто скрыть его, и это будет действительно странно...
Если вы можете найти глупый компилятор С++, который свяжет эту функцию как виртуальную, тогда вы получите вызов виртуальной функции времени выполнения...
Я думаю, что это можно сделать только для взлома, и я понятия не имею, какой взлом действительно...
Ответ 7
Чтобы ответить на ваш первый вопрос, вы можете сделать это, так как пользователи Apple, если они получат конкретный экземпляр, полученный от Персика, не будут знать ничего другого. И экземпляр не будет знать его не Apple (если нет каких-то виртуальных функций от Apple, которые переопределены, о которых вы нам не сообщили).
Я еще не могу представить, насколько полезно было бы переопределить виртуальную функцию с чистой виртуальной - это даже юридическая?
В общем, вы согласны со статьей Скотта Мейерса "Сделать все неклассические классы абстрактными" из своих книг.
В любом случае, кроме того, что вы описываете, кажется, является законным - это просто то, что я не вижу, чтобы вам это нужно часто.