Множественное наследование в C ++, приводящее к затруднению переопределения общей функциональности - PullRequest
4 голосов
/ 11 января 2009

В симуляции физики C ++ у меня есть класс под названием Circle и Square. Это Shapes, и у них есть метод push (), который применяет к нему силу. Затем существует особый случай Circle, называемый SpecialCircle, в котором push () должна демонстрировать немного другие свойства. Но на самом деле есть также SpecialSquare (), которая должна демонстрировать те же свойства силы. Поэтому я хотел бы иметь абстрактный базовый класс под названием Shape, который заботится о кругах и квадратах, но тогда я бы также хотел абстрактный абстрактный класс с именем Special, который применяет специальные свойства к force ().

Какой лучший способ спроектировать структуру этого класса?

Пока у меня есть:

class Shape {
    virtual void push();
};

class Circle : public Shape {};

class Square : public Shape {};

class Special {
    virtual void push();
};

class SpecialCircle : public Circle, Special {};

class SpecialSquare : public Square, Special {};

Конечно, вышеупомянутое не скомпилируется, так как Special :: push () и Shape :: push () конфликтуют. Как и ожидалось, я получаю сообщение «ошибка: запрос на членство push» неоднозначен.

Как я могу реорганизовать мою структуру классов так, чтобы Circle и Square могли совместно использовать определенные свойства друг с другом, но SpecialCircle и SpecialSquare все еще могут наследовать от Shape, а также наследовать измененные функции от Special?

Спасибо.

пс., Это проблема наследования алмазов?

Ответы [ 6 ]

9 голосов
/ 11 января 2009

Другое решение (может соответствовать или не соответствовать вашим потребностям, зависит от деталей вашей реализации):

  • Имейте класс Behavior, и пусть NormalBehavior и SpecialBehavior наследуют его.
  • Имейте класс Shape, и пусть Square и Circle наследуют его. Пусть Shape будет агрегатным типом с членом Behavior (т.е. вы передаете объект Behavior различным конструкторам Shape). Другими словами, пусть Форма имеет Поведение.
  • Делегирование фактических различий в поведении фигур методам иерархии поведения.

И наоборот, вы можете:

  • Имейте класс PhysicalObject, и пусть NormalObject и SpecialObject наследуются от него;
  • Имейте класс Shape, и пусть Square и Circle наследуют от него;
  • Пусть физический объект имеет форму.

Предпочитают агрегацию, а не наследование. Это приложение шаблона Bridge . Преимущество этой стратегии в отношении наличия Square, SpecialSquare, Circle и SpecialCircle заключается в том, что завтра вам нужно будет добавить Rectangle, Hexagon и т. Д., И для каждой добавляемой фигуры вам нужно будет реализовать two классы (дублированный код - зло); это, на мой взгляд, реальная проблема, которую решает Бридж.

3 голосов
/ 11 января 2009

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

У Херба Саттера есть отличная статья о том, как решить вашу проблему: Множественное наследование - Часть III

Короче говоря, вы используете промежуточные классы для «переименования» виртуальных функций. Как говорит Херб:

Переименование виртуальных функций

Если бы две унаследованные функции имели разные сигнатуры, проблем не было бы: мы просто переопределили бы их независимо, как обычно. Хитрость заключается в том, чтобы каким-то образом изменить сигнатуру хотя бы одной из двух унаследованных функций.

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

Вот длинный пример использования ваших классов:

class Shape {
public:
    virtual void push() = 0;
};

class Circle : public Shape 
{
public:
    void push() {
        printf( "Circle::push()\n");
    }
};

class Square : public Shape 
{
public:
    void push() {
        printf( "Square::push()\n");
    }
};

class Special {
public:
    virtual void push() = 0;
};


class Circle2: public Circle
{
public:
    virtual void pushCircle() = 0;
    void push() {
        pushCircle();
    }
};

class Square2: public Square
{
public:
    virtual void pushSquare() = 0;
    void push() {
        pushSquare();
    }
};


class Special2 : public Special
{
public:
    virtual void pushSpecial() = 0;
    void push() {
        pushSpecial();
    }
};



class SpecialCircle : public Circle2, public Special2 
{
public:
    void pushSpecial() {
        printf( "SpecialCircle::pushSpecial()\n");
    }
    void pushCircle() {
        printf( "SpecialCircle::pushCircle()\n");
    }

};

class SpecialSquare : public Square2, public Special2 
{
public:
    void pushSpecial() {
        printf( "SpecialSquare::pushSpecial()\n");
    }
    void pushSquare() {
        printf( "SpecialSquare::pushSquare()\n");
    }

};

int main( int argc, char* argv[])
{
    SpecialCircle sc;
    SpecialSquare ss;

    // sc.push();   // can't be called - ambiguous
    // ss.push();
    sc.pushCircle();
    ss.pushSquare();

    Circle* pCircle = ≻
    pCircle->push();

    Square* pSquare = &ss;
    pSquare->push();

    Special* pSpecial = ≻
    pSpecial->push();

    pSpecial = &ss;
    pSpecial->push();


    return 0;
}
2 голосов
/ 11 января 2009

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

Если вы не знакомы с техникой, выполните поиск в SO или Google. Убедитесь, что вы ищете «mixin» и «CURLYURURRING Template Pattern». Есть куча отличных статей, чтобы вы начали.

1 голос
/ 11 января 2009

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

class SpecialCircle : public Circle, Special {
  public:
    virtual void push() { Special::push(); }
};
class SpecialSquare : public Square, Special {
  public:
    virtual void push() { Special::push(); }
};

Но в этом случае я думаю, что правильный ОО-подход состоит в том, чтобы вычленить поведение толчка в своем классе, как предположил Федерико Рампони.

0 голосов
/ 11 января 2009

Что ж, если к специальным и нормальным окружностям можно приложить как силы, так и к специальному кругу есть другой метод, который применяет специальные силы, почему бы не иметь два интерфейса и два метода?

struct Applicable {
    virtual ~Applicable() { }

    // if it applies force, better be explicit with naming it.
    virtual void applyForce() = 0;
};

struct SpecialApplicable {
    virtual ~SpecialApplicable() { }

    virtual void applySpecialForce() = 0;
};

struct Shape {
    virtual ~Shape() { }
    Size getSize();
    Point getPosition();
    // ...
};

struct Circle : Shape, Applicable {
    virtual void applyForce() { /* ... */ }
}

struct SpecialCircle : Circle, SpecialApplicable {
    virtual void applySpecialForce() { /* .... */ } 
};

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

struct Circle : Shape, Applicable {
    virtual void applyForce() { /* ... */ }
}

struct SpecialCircle : Circle {
    // applies force, but specially
    virtual void applyForce() { /* .... */ } 
};

Вы также можете поместить applyForce в класс Shape. Это также зависит от среды, в которой эти классы используются. В любом случае, вам действительно следует избегать использования одного и того же метода в двух базовых классах, которые появляются в двух разных базовых решетках. Потому что это неизбежно приведет к таким проблемам неоднозначности. Наследование diamond - это когда вы используете виртуальное наследование. Я полагаю, что есть другие хорошие ответы на stackoverflow, объясняющие это. Это не применимо к вашей проблеме, потому что неоднозначность возникает из-за того, что метод появляется в двух объектах базового класса разных типов. (Он решает только те случаи, когда базовые классы имеют один и тот же тип. В этих случаях он объединит базовые классы и будет содержать только один подобъект базового класса - унаследованный виртуальным наследованием)

0 голосов
/ 11 января 2009

Иметь SpecialShape от Shape и SpecialCircle и SpecialSquare от SpecialShape.

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