Цепочка методов + наследование не очень хорошо сочетаются друг с другом? - PullRequest
14 голосов
/ 15 февраля 2009

Рассмотрим:

// member data omitted for brevity

// assume that "setAngle" needs to be implemented separately
// in Label and Image, and that Button does need to inherit
// Label, rather than, say, contain one (etc)

struct Widget {
    Widget& move(Point newPos) { pos = newPos; return *this; }
};

struct Label : Widget {
    Label& setText(string const& newText) { text = newText; return *this; }
    Label& setAngle(double newAngle) { angle = newAngle; return *this; }
};

struct Button : Label {
    Button& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
        return *this;
    }
};

int main() {
    Button btn;

    // oops: Widget::setText doesn't exist
    btn.move(Point(0,0)).setText("Hey");

    // oops: calling Label::setAngle rather than Button::setAngle
    btn.setText("Boo").setAngle(.5); 
}

Какие-нибудь методы, чтобы обойти эти проблемы?

Пример: использование магии шаблона для создания Button :: move return Button & или что-то в этом роде.

edit Стало ясно, что вторая проблема решается путем создания виртуального setAngle.

Но первая проблема остается неразрешенной разумным образом!

edit : Ну, я думаю, это невозможно сделать правильно в C ++. В любом случае, спасибо за усилия.

Ответы [ 15 ]

1 голос
/ 15 февраля 2009

Я думаю (я не проверял это), это будет сделано с использованием шаблонов:

template<class T> struct TWidget {
    T& move(Point newPos) { pos = newPos; return (T&)*this; }
};

template<class T> struct TLabel : TWidget<T> { ... }

struct Label : TLabel<Label> { ... }

struct Button : TLabel<Button> { ... }

Примечания:

  • Любой / каждый базовый класс должен быть шаблоном, с отдельным листовым классом без шаблонов вверху (в отличие от классов LabelT и Label).
  • Актер может быть dynamic_cast, если хотите.
  • Вместо приведения "return *this" базовый класс может содержать T& в качестве члена данных (производный класс будет передавать this в конструктор базового класса), который будет дополнительным элементом данных , но это позволяет избежать приведения, и я думаю, что может разрешить композицию вместо или так же как наследование.
0 голосов
/ 10 мая 2009

Действительно, цепочка методов - плохая идея из-за плохой безопасности исключений. Вам это действительно нужно?

0 голосов
/ 24 февраля 2009

Другие люди сталкивались с проблемами дизайна. Вы можете обойти эту проблему (хотя и довольно грубо), используя поддержку C ++ для ковариантных типов возвращаемых данных.

struct Widget {
    virtual Widget& move(Point newPos) { pos = newPos; return *this; }
};

struct Label : Widget {
    Label& move(Point newPos) { pos = newPos; return *this; }
    Label& setText(string const& newText) { text = newText; return *this; }
    Label& setAngle(double newAngle) { angle = newAngle; return *this; }
};

struct Button : Label {
    Button& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
        return *this;
    }
};

int main() {
    Button btn;

    // oops: Widget::setText doesn't exist
    btn.move(Point(0,0)).setText("Hey");

    // oops: calling Label::setAngle rather than Button::setAngle
    btn.setText("Boo").setAngle(.5); 
}

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

struct Widget {
    void move(Point newPos) { pos = newPos; }
};

struct Label : Widget {
    void setText(string const& newText) { text = newText; }
    void setAngle(double newAngle) { angle = newAngle; }
};

struct Button : Label {
    void setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
    }
};

int main() {
    Button btn;

    // oops: Widget::setText doesn't exist
    btn.move(Point(0,0));
    btn.setText("Hey");

    // oops: calling Label::setAngle rather than Button::setAngle
    btn.setText("Boo");
    btn.setAngle(.5); 
}
0 голосов
/ 16 февраля 2009

Действительно ли setText () и setAngle () должны возвращать свои собственные типы в каждом классе? Если вы установите их все для возврата Widget &, то вы можете просто использовать виртуальные функции следующим образом:

struct Widget {
    Widget& move(Point newPos) { pos = newPos; return *this; }
    virtual Widget& setText(string const& newText) = 0;
    virtual Widget& setAngle(double newAngle) = 0;
};

struct Label : Widget {
    virtual Widget& setText(string const& newText) { text = newText; return *this; }
    virtual Widget& setAngle(double newAngle) { angle = newAngle; return *this; }
};

struct Button : Label {
    virtual Widget& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
        return *this;
    }
};

Обратите внимание, что даже если тип возвращаемого значения - Widget &, функции уровня кнопки или метки по-прежнему будут вызываться.

0 голосов
/ 15 февраля 2009

Ну, вы знаете, что это Button, поэтому вы должны иметь возможность разыграть возвращенное Widget& как Button& и продолжать. Хотя это выглядит немного некрасиво.

Еще один довольно раздражающий вариант - создать оболочку в вашем классе Button для функции Widget::move (и друзей). Вероятно, не стоит усилий, чтобы обернуть все, если у вас есть больше, чем несколько функций.

...