在我的情况下,多重继承是一个好的设计模式吗?

时间:2017-05-18 14:33:06

标签: c++ oop inheritance design-patterns

我有以下问题: 我希望有多个Windows都显示某种功能图。假设一个窗口应显示坐标轴并具有一些交互功能,另一个窗口也应显示坐标轴并具有设置对话框。

我是如何直观地实现这一点的,是将Window实现为使用我的框架(在我的案例中为VTK)执行基本窗口初始化的一些类,一些虚拟继承类WindowWithAxisWindowInteractingWindowWithSettingsDialog,然后

  • 班级A继承WindowInteractingWindowCoordinateAxis
  • 班级B继承WindowWithSettingsDialogWindowCoordinateAxis

因此,通过互联网阅读我现在到处都可以看到(例如Google风格指南),我应该WindowWindowInteractingWindowCoordinateAxis,{{1}都是纯抽象类/接口。

这是我不明白的。这是否意味着我不允许实际实现这些类,并且我必须在类WindowWithSettingsDialog和类{{1}中实现WindowWindowCoordinateAxis的所有功能(以及使用这些的所有其他类)?这对我来说似乎不是一个干净的解决方案。

这种“菱形继承”应该是一个经常出现的问题,那么我可以通过什么方式来解决这个问题呢?

2 个答案:

答案 0 :(得分:0)

一般原则是避免重复代码/功能并提高可重用性。在您的情况下,WindowInteractingWindowCoordinateAxis类都具有相同的功能(来自Window)。相反,要么定义一个名为IInteractingIHasTheCoordinateAxis的接口(具有纯虚函数的类)并提供它们的具体实现,或者根据传递的参数使您的Window类行为不同

答案 1 :(得分:0)

我想我会采用以下方式:

定义一个多态Window基类,它是处理UI事件的接口。

从中导出一个WindowImpl,它是使用Feature类列表进行模板化的。每个功能都可以选择是否响应事件。

WindowImpl通过枚举一组特征来调用每个Feature对象上的事件代码。功能可能会保持自己的状态。

e.g:

#include <utility>
#include <tuple>
#include <iostream>

/// Basic concept of a window that handles events
struct Window
{
    Window(std::string title)
        : title_(std::move(title)) {}

    // all windows handle clicks, but defer to a private polymorphic implementation
    void onClickOrWhatever()
    {
        handleOnClickOrWhatever();
    }

    // all windows have a title
    auto title() const -> const std::string&
    {
        return title_;
    }

private:
    // make ploymorphism an implementation detail
    virtual void handleOnClickOrWhatever() = 0;

    std::string title_;
};

// All concrete windows are a WindowImpl customised with a feature list
template<class...Features>
struct WindowImpl
    : Window
{
    static constexpr auto FeatureCount = sizeof...(Features);

    using Window::Window;

private:

    // the onClick handler defers to actions specified by each enabled feature
    void handleOnClickOrWhatever() override
    {
        std::cout << title() << " detects a click (or whatever)\n";
        implHandleOnClickOrWhatever(features_,
                                    std::make_index_sequence<FeatureCount>());
        std::cout << std::endl;
    }

    template<class FeatureTuple, std::size_t...Is>
    void implHandleOnClickOrWhatever(FeatureTuple&& features, std::index_sequence<Is...>)
    {
        using expand = int[];
        void(expand{0,
                    (std::get<Is>(features).handleClick(*this), 0)...
        });

    }


    using FeatureTuple = std::tuple<Features...>;
    FeatureTuple features_;
};



// a base feature that takes no action
struct BaseFeature
{
    template<class Window>
    static void handleClick(Window& window)
    {
        // don't respond
    }
};

//
// some features
//

struct HasAxis
    : BaseFeature
{
    template<class Window>
    static void handleClick(Window& window)
    {
        std::cout << "testing touch on axis" << std::endl;
    }
};

struct HasSettingsDialog
    : BaseFeature
{
    template<class Window>
    static void handleClick(Window& window)
    {
        std::cout << "testing touch on settings button" << std::endl;
    }
};

struct HasAlphaBackground
    : BaseFeature
{
    // note : does not handle clicks
};

// some customised windows, mixing anf matching features
using WindowWithAxis = WindowImpl<HasAxis>;
using WindowWithAxisAndSettings = WindowImpl<HasAxis, HasSettingsDialog>;
using WindowWithAxisAndAlpha = WindowImpl<HasAxis, HasAlphaBackground>;

int main()
{
    WindowWithAxis w{"window 1"};
    w.onClickOrWhatever();

    WindowWithAxisAndSettings w2{"window 2"};
    w2.onClickOrWhatever();

    WindowWithAxisAndAlpha w3{"window 3"};
    w3.onClickOrWhatever();
}

预期产出:

window 1 detects a click (or whatever)
testing touch on axis

window 2 detects a click (or whatever)
testing touch on axis
testing touch on settings button

window 3 detects a click (or whatever)
testing touch on axis