【问题标题】:About Interface Class usage in C++关于 C++ 中的接口类使用
【发布时间】:2014-10-16 08:53:02
【问题描述】:

我对 C++ 中接口类的使用有疑问,但不知道它的名称以及如何搜索它。希望你能帮助我,好心。

我将尝试用一个简单的例子来说明我的问题。

我有 5 种不同的可能对象,例如 三角形、正方形、矩形、五边形和六边形

所有这些对象都有共同的属性,所以我将有一个接口类Shape

现在,我想要的是:我将拥有一个 Shape 类对象,并且由于运行时的选择,我希望能够将其用作其他 5 个对象之一。

所以我做了如下的事情:

class Shape 
{
public:

    virtual int getArea()=0;

    virtual void setWidth(int w)
    {
        width = w;
    }

    virtual void setHeight(int h)
    {
        height = h;
    }


protected:
    int width;
    int height;
};



class Triangle: public Shape
{
public:

    int getArea()
    { 
        return (m_Width * m_Height)/2; 
    }

};

class Rectangle: public Shape
{
public:

    int getArea()
    { 
        return (m_Width * m_Height); 
    }

};

使用时,我只想创建一个 Shape 的对象,并用其中一个派生类对其进行初始化。所以,从那时起,我希望它表现得像这样的对象的一个​​实例:

void main(){

 Shape* shape;

 std::string shapeType;

 std::cout<<"Enter Shape Type: triangle or rectangle."<<std::endl;

 std::cin>>shapeType;


 if (shapeType == "triangle")
     shape = new Triangle();
 else if (shapeType == "rectangle")
     shape = new Rectangle();


 shape->setWidth(5);
 shape->setHeight(7);

 std::cout<<shape->getArea()<<std::endl;


}

到这里没问题。问题从这里开始。这些派生类可能有不同的属性、方法。当我将这些方法添加到它们自己的类时,shape 对象无法访问它(正确)。可以使用的另一种方法是将新的派生对象转换为 shape 对象,例如:

Triangle* triangle = (Triangle*)shape;
// now I can access own attributes of Triangle object.

但这并不是你想的那样处理它的好方法。除此之外,我只知道一种方法迫使我将所有这些属性写入 Shape 类并在所需的派生类中实现它们,如果不需要其他类,则将其实现为空。

你有什么好的办法解决这个问题吗?我相信会有,但我对这个主题没有太多经验,所以希望你有一个适合我想要的解决方案。

提前致谢。

【问题讨论】:

  • 这个问题没有好的解决办法。你可以用大部分无用的虚函数填充你的基类,或者使用 dynamic_cast hacks、标记的联合、访问者模式……所有这些都是坏的。选择你的毒药。
  • 不要忘记虚拟析构函数。

标签: c++ interface polymorphism abstract-class


【解决方案1】:

首先,您的Shape 不是接口,而是基本实现类。将它们分开。

接口是一个只有纯虚方法和虚析构函数的抽象类:

struct IShape
{
    virtual int getArea() =0;
    virtual void setWidth(int w) =0;
    virtual void setHeight(int h) =0;
    // without virtual destructor you cannot
    // properly destroy object through IShape pointer
    virtual ~IShape() {}
};

现在让我们编写基本实现:

class CShapeBaseImpl : public IShape
{
public:
    void setWidth(int w) override { width_ = w; }
    void setHeight(int h) override { height_ = h; }
protected:
    int width_ = 0;
    int height_ = 0;
};

在大多数情况下,您希望创建一些具体的对象并仅通过它的界面使用它。通常是通过abstract factory pattern

std::unique_ptr<IShape> CreateTriangle( int a, int b, int c )
{
    auto triangle = std::make_unique<Triangle>(a,b,c);
    // there you can work with it as Triangle
    return triangle;
}

CreateTriangle 返回后最好忘记它是Triangle。现在才IShape

【讨论】:

  • 为什么不返回一个 unique_ptr?如果他们想放弃它的安全性,应该由调用者决定。
  • @Neil,你是对的。我经常使用 dll 并使用 COM 样式的接口。这是我的习惯。 =)
  • 你可能需要std::move,不确定
  • @Neil, std::move 没有必要在那里:open-std.org/jtc1/sc22/wg21/docs/papers/2002/…
【解决方案2】:

如果您的类型是Shape,则意味着您打算将其用作Shape。如果您打算使用特定子类之一,请使用该特定类型。

如果你仍然真的想要一个存储的泛型类型并且仍然想要使用一些特定的操作,那么你需要将你的指针向下转换为带有static_cast 的子类指针,但你要么需要知道哪个类型存储在那里或使用dynamic_cast,在这两种情况下,这都像是糟糕的设计。

您应该能够使用虚函数执行所有多态操作,或者根据情况和您的需要,您可以使用其他设计模式,例如double dispatch

【讨论】:

    【解决方案3】:

    所有动物都会吠叫吗?

    考虑像这样的层次结构

    -> animal
       -> dog 
          -> great_dane       
          -> chiwawa
       -> fish
          -> tuna
          -> shark
    

    有些人认为,如果你有一个animal*-s 的集合,并且你想让它们吠叫,animal 必须声明一个抽象的virtual void bark()=0 才能在great_dane 和@987654326 中适当地实现@(第一个是cout &lt;&lt; "BARK",第二个是cout &lt;&lt; "bark"),而fish 只是将其实现为“什么都不做”。

    如果您想知道有什么东西在吠叫,只需询问并聆听即可。没有声音?没有。

    还有人(包括我自己)认为这样的做法会导致animal宣布动物园的所有诗句(包括尚不存在的物种)因此使动物类成为一种“上帝对象”(整个宇宙的最终知识所有者),但事实并非如此。

    对于这些人来说,bark() 属于dog,让动物吠叫基本上没有意义,除非你事先检查它实际上是一只狗。 之后,它会根据自己的种族吠叫。

    要检查动物是否是狗,给定animal* myanimal,您只需转换为dog* mydog = dynamic_cast&lt;dog*&gt;(myanimal)

    • mydog 是否为空?对不起:那不是狗:不要这样对待它。坏事可能会发生。
    • 它是非空的吗?很好:现在你已经拥有了所有的“狗功能” 一眼。忘记fish-es

    如果你觉得演员阵容过于丑陋和频繁,你可以在animal中实现一个辅助函数:

    template<class T>
    T* animal::as_a() { return dynamic_cast<T*>(this); }
    

    这样你就可以拥有

    auto mydog = myanimal->as_a<dog>();
    if(mydog) mydog->bark();
    

    现在,把动物变成形状,把狗变成长方形,把鱼变成三角形!

    【讨论】:

      【解决方案4】:

      如果属性只与派生相关,而与基无关,则在基中实现它是没有意义的。例如。 isEquilateral() 属性应该在 Triangle 类中实现,而不是在 Shape 基类中实现。

      所以你可以做的是使用Type() 属性,该属性在每个派生类中重新实现,给出派生对象类型(三角形、矩形等...)

      然后是静态转换:

      Triangle *triangle = static_cast<Triangle*>(shape);
      

      所以是这样的:

      if (shape->Type() == Shape::Triangle){
          Triangle *triangle = static_cast<Triangle*>(shape);
          bool eq = triangle->isEquilateral();
          ...
      }
      

      或者简单地说:

       Triangle *triangle = static_cast<Triangle*>(shape);
       if (triangle){
           bool eq = triangle->isEquilateral();
           ...
       }
      

      但我更喜欢第一种方法。

      【讨论】:

        【解决方案5】:

        我相信您需要使用指向基类的指针来存储 Object。所以你有Shape* 用于存储new Triangle(),你应该能够访问必要的函数和变量。这是基于记住如何在向量中执行此操作,这更好here,但我不明白为什么它不会转移。

        Shape* shape;
        shape = new Triangle();
        //shape->foo accesses Triangle methods
        

        要么就是这样,要么使用动态/静态转换在运行时将其转换为正确的类型,尽管这意味着您需要检查它是什么类型才能进行正确的转换(所以可能有一个识别每个子类设置的基类中的字符串或其他内容)

        【讨论】:

          【解决方案6】:

          这绝对不能按照您解释的方式工作。它不是遗产的感觉。您应该阅读有关遗传的信息。基本上它的以下规则:如果你创建一个从属你可以使用父亲的功能和属性,如果它们是公共的或受保护的

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-09-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多