Вывод абстрактного класса из конкретного класса - PullRequest
13 голосов
/ 22 ноября 2008

Допустим, у нас есть бетон class Apple. (Объекты Apple могут быть созданы.) Теперь кто-то приходит и получает резюме class Peach от Apple. Это абстракция, потому что она вводит новую чисто виртуальную функцию. Пользователь Peach теперь вынужден извлечь из него и определить эту новую функцию. Это общий шаблон? Это правильно делать?

Пример:


class Apple
{
public:
    virtual void MakePie();
    // more stuff here
};</p>

<p>class Peach : public Apple
{
public:
    virtual void MakeDeliciousDesserts() = 0;
    // more stuff here
};
Теперь скажем, у нас есть бетон class Berry. Кто-то выводит реферат class Tomato от Берри. Он абстрактный, потому что он перезаписывает одну из виртуальных функций Берри и делает ее чисто виртуальной. Пользователь Tomato должен повторно реализовать функцию, ранее определенную в Berry. Это общий шаблон? Это правильно делать?

Пример:


class Berry
{
public:
    virtual void EatYummyPie();
    // more stuff here
};</p>

<p>class Tomato : public Berry
{
public:
    virtual void EatYummyPie() = 0;
    // more stuff here
};
Примечание. Имена придуманы и не отражают какой-либо реальный код (надеюсь). При написании этого вопроса не пострадали фрукты.

Ответы [ 7 ]

8 голосов
/ 22 ноября 2008

Re Peach от Apple:

  • Не делайте этого, если Apple является классом значений (т.е. имеет копию ctor, не идентична экземпляры могут быть равны и т. д.). Смотри Мейерс Более эффективный C ++, пункт 33, почему.
  • Не делайте этого, если у Apple есть публичный не виртуальный деструктор, иначе ты приглашать неопределенное поведение, когда ваш пользователи удаляют Apple через указатель на персик.
  • В противном случае вы, вероятно, в безопасности, потому что вы не нарушили заменяемость Лискова . Персик IS-A Apple.
  • Если вы владеете кодом Apple, предпочтите выделить общий абстрактный базовый класс (возможно, Fruit) и извлечь из него Apple и Peach.

Re Помидор от Берри:

  • То же, что и выше, плюс:
  • Избегайте, потому что это необычно
  • Если необходимо, задокументируйте, что должны делать производные классы Tomato, чтобы не нарушать заменяемость по Лискову. Функция, которую вы переопределяете в Berry, назовем ее Juice(), накладывает определенные требования и дает определенные обещания. Реализации Juice() производных классов не должны требовать больше и обещать не меньше. Тогда DerivedTomato IS-A Berry и код, который знает только о Berry, безопасны.

Возможно, вы выполните последнее требование, задокументировав, что DerivedTomatoes должен вызывать Berry::Juice(). Если это так, рассмотрите возможность использования шаблона вместо:

class Tomato : public Berry
{
public:
    void Juice() 
    {
        PrepareJuice();
        Berry::Juice();
    }
    virtual void PrepareJuice() = 0;
};

Теперь есть отличный шанс, что ягода томата IS-A вопреки ботаническим ожиданиям. (Исключение составляют случаи, когда реализации производных классов PrepareJuice налагают дополнительные предварительные условия, помимо тех, которые налагаются Berry::Juice).

5 голосов
/ 22 ноября 2008

Это может показаться плохим дизайном. Может быть принудительно, если вы хотите взять конкретное определение из закрытой библиотеки, расширить его и разложить на нем кучу вещей, но на этом этапе я бы серьезно подумал о руководящих принципах, касающихся инкапсуляции по наследованию. , вы, вероятно, должны.

Да, чем больше я об этом думаю, это очень плохая идея.

2 голосов
/ 22 ноября 2008

Если вы используете рекомендованную практику наличия модели наследования "is-a", то этот шаблон почти никогда не появится.

Как только у вас есть конкретный класс, вы говорите, что вы можете создать его экземпляр. Если вы потом выводите из него абстрактный класс, то что-то, что является атрибутом базового класса, не относится к производному классу, который должен устанавливать клаксоны, что что-то не так.

Глядя на ваш пример, персик - это не яблоко, поэтому его не следует выводить. То же самое относится и к помидорам, полученным из ягод.

Здесь я обычно советую сдерживание, но это даже не кажется хорошей моделью, поскольку Apple не содержит персика.

В этом случае я бы выделил общий интерфейс - PieFilling или DessertItem.

2 голосов
/ 22 ноября 2008

Не обязательно неправильно , но определенно вонючий. Особенно, если вы оставляете фрукты на солнце слишком долго. (И я не думаю, что мой дантист хотел бы, чтобы я ел конкретные яблоки.)

Хотя главное, что я вижу здесь, это вонючий не столько абстрактный класс, производный от конкретного класса, сколько иерархия наследования REALY DEEP.

РЕДАКТИРОВАТЬ: перечитывая, я вижу, что это две иерархии. Все фрукты перепутали меня.

1 голос
/ 22 ноября 2008

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

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;
}
0 голосов
/ 22 ноября 2008

Чтобы ответить на ваш первый вопрос, вы можете сделать это, поскольку пользователи Apple, если им дать конкретный экземпляр, полученный из Peach, не будут знать ничего другого. И экземпляр не будет знать, что это не Apple (если только некоторые виртуальные функции от Apple не отменены, о которых вы нам не сообщили).

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

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

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

0 голосов
/ 22 ноября 2008

Хммм ... подумав "что за ....." на пару секунд, я прихожу к выводу, что это не обычное дело ... Кроме того, я бы не стал выводить Персик из Яблока и Томат из Берри ... У вас есть лучший пример? :)

Это много странного дерьма, которое ты можешь делать в C ++ ... Я даже не могу думать о 1% этого ...

О переопределении виртуального с чисто виртуальным, вы, вероятно, можете просто скрыть его, и это будет действительно странно ...

Если вы можете найти глупый компилятор C ++, который связывал бы эту функцию как виртуальную, тогда вы получите чистый вызов виртуальной функции во время выполнения ...

Я думаю, что это можно сделать только для взлома, и я понятия не имею, что за взлом на самом деле ...

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