Вывод абстрактного класса из конкретного класса

Скажем, у нас есть конкретный 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, которые переопределены, о которых вы нам не сообщили).

Я еще не могу представить, насколько полезно было бы переопределить виртуальную функцию с чистой виртуальной - это даже юридическая?

В общем, вы согласны со статьей Скотта Мейерса "Сделать все неклассические классы абстрактными" из своих книг.

В любом случае, кроме того, что вы описываете, кажется, является законным - это просто то, что я не вижу, чтобы вам это нужно часто.