【问题标题】:Design an interface with appropriate declarations of Base class pointers - cpp使用适当的基类指针声明设计接口 - cpp
【发布时间】:2018-01-10 08:51:34
【问题描述】:

我有一点设计问题。

假设我有这个接口:

class IBase {
public:
    virtual void Run() = 0;
    virtual void DoSomethingWithData(IData* data) = 0;
    virtual ~IBase() = 0;
};

我想实现派生类,所以我需要为IData构建一个派生类(我们称之为DData)。 IData 基本上是一个空接口:

class IData {
public:
    IData();
    virtual ~IData() = 0;
};

问题是在我实现的 DoSomethingWithData 方法中 - 我需要引用 DData 中的特定成员,但我不能因为指针是 IData 。例如,查看某些派生类中 DoSomethingWithData 的实现:

class DData : public IData {
private:
    int m_i;
};

<Derived.h : IBase>
Derived::DoSomethingWithData(IData* data) {
    some_function_call(data->m_i) // cannot be used by the base ptr
}

解决这个问题的最佳方法是什么?

SetData 上的动态转换? 更改界面(以这种方式?)? 其他选择?

编辑:

为了澄清问题: 我有这种关系 -

class A -> uses DataA
class B -> uses DataB
class C -> uses DataC

这些类有一个通用接口:Run、DoSomethingWithData。 DoSomethingWithData 期望数据,但它不知道数据是什么,它取决于我们实现的类。 我需要找到编写接口的最佳方式,而不是使用像 dynamic_cast 这样糟糕的 OOP 技术。

【问题讨论】:

  • 您的问题不清楚。请提供一些关于设计应该可以做什么的解释,也许应该用设计来防止什么。你能提供代码来演示应该如何使用设计吗?
  • 如果要对所有类进行不同的处理,那么IData 真的不是它们的接口。空接口本身就是可疑的——这些类有什么共同点?什么都没有?
  • IBase 说它接受一个指向 IData 的指针。 每个必须接受指向IData的any指针。您不能选择“D1Derived 将接受指向 D1Data 的指针,而 D2Derived 将接受指向 D2Data 的指针”。这是他们应该在 OO 编程课程中教授的第一件事,但不知何故没有人这样做,所以我必须为他们所有,坦率地说我厌倦了它。
  • @n.m.所以你建议从接口中删除 DoSomethingWithData 函数,并在每个派生类中使用合适的 DData 实现它?
  • 叹息。回到一号广场。如果您希望每个派生类都接受 any 类型的 IData,请继续在您的基类中声明您的方法。否则,您的派生类没有实现相同的接口,并且您没有任何东西可以放入基类中。

标签: c++ design-patterns interface


【解决方案1】:

我需要引用 DData 中的特定成员,但我不能因为指针是 IData 的

接口指定一个契约。它不“知道”内部细节/表示(也就是成员变量、私有函数等)。如果您的IData 必须“知道”DData 的私有成员变量,那么您做错了什么。也许使用Data 的接口是错误的。如果Data 是一个POD(普通数据)结构,那么只需让您的基类采用Data 并公开一个处理所述结构的API。

【讨论】:

    【解决方案2】:

    问题是在我实现的DoSomethingWithData 方法中 - 我需要引用DData 中的特定成员,但我不能因为指针是IData

    您正在传递一个指向IData 的指针,它是DData 的基类。通过虚函数调度,您可以通过指向IData 的指针调用实际在DData 中实现的成员函数。

    DData派生自IData,所以你可以在IData中声明一个虚成员函数

    class IData {
    public:
        IData();
        virtual int getData() = 0; // <-- new virtual function
        virtual ~IData() = 0;
    };
    

    然后在DData实现这个函数:

    class DData : public IData {
    public:
        int getData() override { // <-- implementation
           return m_i;
        }
    private:
         int m_i;
    };
    

    由于该成员函数在DData中实现,它可以访问其私有数据成员(即:m_i)。

    当您通过IData 指针调用DData 实例的getData() 时,您将使用DData 的实现。

    【讨论】:

    • 你是对的,但问题是我的数据远比“int m_i”复杂,每个数据派生类可以有不同的数据。一个类可以有 (string s, int i, double d) 另一个可以有 (vector v, float f)
    • @BinyaminAppelbaum 通过dynamic_cast&lt;&gt; 执行downcast 是否可行?
    • 我愿意这样做,因为这是解决问题的最简单方法。但是 dynamic_cast 通常指向糟糕的设计......所以我希望有另一个解决方案
    • @BinyaminAppelbaum 我的意思是,您可以在派生类中保留有关实现的信息,在选择要动态转换到的派生类之前访问此信息(通过虚函数)。
    • 对不起,我没听懂。你的意思是我需要 dynamic_cast(data) DoSomethingWithData 函数处的指针吗?
    【解决方案3】:

    我们选择的解决方案是使用模板(我不明白为什么没有人建议它)。

    template <class T>
    class IBase {
    public:
        virtual void Run() = 0;
        virtual void DoSomethingWithData(const T& data) = 0;
        virtual ~IBase() = 0;
    };
    

    我认为在这种情况下这是最好的解决方案,因为它不需要任何人从空接口派生,并且它确实声明了一些结构。

    我想知道是否有人对此解决方案有任何保留。

    【讨论】:

    • 这里每个IBase 类都将属于不同的类型,具体取决于它接受的data 类型。没错,这不是您在原始问题中所要求的,并且暗示这正是 cmets 中指出的内容。
    • 我要求一个合适的设计......就是这样。
    猜你喜欢
    • 1970-01-01
    • 2011-12-28
    • 1970-01-01
    • 2014-12-01
    • 2017-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多