【问题标题】:How to do different things according to type but not using overriding?如何根据类型做不同的事情但不使用覆盖?
【发布时间】:2015-12-07 02:14:33
【问题描述】:

例如,我有一些 Shape,每个 shape 返回不同类型的按钮:

#include <stdio.h>
struct Button{
    Button(){
        printf("Button\n");
    }
};

struct CircleButton : public Button{
    CircleButton(){
        printf("CircleButton\n");
    }
};

struct SquareButton : public Button{
    SquareButton(){
        printf("SquareButton\n");
    }
};

struct Shape{
    virtual Button* getNewButton()=0;
};

struct Circle : public Shape{
    Button* getNewButton(){
        return new CircleButton();
    }
};

struct Square : public Shape{
    Button* getNewButton(){
        return new CircleButton();
    }
};

通过使用覆盖,我可以编写一些通用代码:

int main(){
    Shape* s=new Circle();
    Button* b=s->getNewButton();
    return 0;
}

但现在shape是数据传输对象,它不应该有任何方法:

struct Shape{
};

struct Circle : public Shape{
};

struct Square : public Shape{
};

我想保留我的通用代码,我试过了:

struct Helper{
    static Button* getNewButton(Shape* s){
        return new Button();
    }

    static Button* getNewButton(Circle* s){
        return new CircleButton();
    }

    static Button* getNewButton(Square* s){
        return new SquareButton();
    }
};

int main(){
    Shape* s=new Circle();
    Button* b=Helper::getNewButton(s);
    return 0;
}

但是这次我不能得到一个新的CircleButton,如何修改代码,或者可以应用什么设计模式,这样我可以根据不同的形状得到不同类型的按钮,但是没有实现Shape,Circle和中的任何方法正方形?

【问题讨论】:

  • 一个除了简单地返回另一个类的新实例什么都不做的类有什么意义?它们可能只是独立的功能。
  • 您可以查看使用Vistor Pattern
  • 为什么?您对重写虚函数有何反感?
  • 对没有虚拟或不虚拟方法的数据传输对象使用继承有什么意义?如果 Circle 没有任何行为,为什么还要继承 Shape?

标签: c++ oop design-patterns overriding generic-programming


【解决方案1】:

Factory Design Pattern 最有可能解决您的问题。

您可以使用 RTTI 来检查按钮的类型(使 Button 的析构函数为 virtual 将允许您使用 dynamic_cast),并且仅当按钮已经是正确的类型时才允许使用以下方法进行捕获:

struct Shape {
    // Store the allocated button
    unique_ptr<Button> ptr;

    Shape()
    {
       ptr.reset(nullptr);
    }
    Shape(Button* otherButton)
    {
       ptr.reset(otherButton); 
    }

    virtual bool containsValidButton()
    {
        return ptr != nullptr;
    }
};

struct Circle : public Shape {
    Circle(Button* otherButton)
    {
        ptr.reset(dynamic_cast<Circle*>(otherButton) ? otherButton : nullptr);
    }
};

这保留了一个通用接口,但如果用户将不正确的按钮类型传递给构造函数,则不会存储按钮。

您可以使用的工厂方法将涉及创建一个枚举参数(如上一个链接中的示例所述)以向您的工厂方法表明要创建的 Button 类型:

enum ButtonType
{
    SQUARE,
    CIRCLE
};

static unique_ptr<Button> GetNewButton(ButtonType bType)
{
    unique_ptr<Button> ptr;
    switch (bType)
    {
    case SQUARE:
        ptr.reset(new SquareButton());
        break;
    case CIRCLE:
        ptr.reset(new CircleButton());
        break;
    }

    return ptr;
}

可以在 Square 或 Circle 的构造函数中使用类似的方法(再次使用 RTTI):

static unique_ptr<Button> GetNewButton(Shape* shape)
{
    unique_ptr<Button> ptr;
    if (dynamic_cast<Circle*>(shape))
    {
        ptr.reset(new CircleButton());
    }
    if (dynamic_cast<Square*>(shape))
    {
        ptr.reset(new SquareButton());
    }
    return ptr;
}

struct Square : public Shape {
    Square()
    {
        ptr = GetNewButton(this);
    }
};

如果您对 dynamic_cast 在上述情况下如何发挥其魔力感到好奇,请参阅RTTI 上的文章。

注意:您可能必须使用前向声明(如果不包括头文件中的声明)才能编译上述 sn-p。

【讨论】:

    【解决方案2】:

    如果您的类中不允许任何方法,我看不到任何设计解决方案可以解决此问题。只有类 ID、Enum 或 instanceOf 等某种类型的类型机制才能让您在运行时确定您正在处理的对象的类型。显然不建议特别使用 instanceOf ,因为它违反了DIP

    但是我很好奇你为什么禁止任何内部方法?您是否意识到这是封装的全部本质?

    如果您不是指任何方法,而是指内部结构,那么在这种情况下,Visitor 模式是一个很好的解决方案。

    【讨论】:

      【解决方案3】:

      boost::variant 可能会有所帮助

      using ShapeVariant = boost::variant<Circle, Square>;
      
      class to_button : public boost::static_visitor<std::unique_ptr<Button>>
      {
      public:
          std::unique_ptr<Button> operator ()(const Circle&) const
          {
              return std::make_unique<CircleButton>();
          }
      
          std::unique_ptr<Button> operator ()(const Square&) const
          {
              return std::make_unique<SquareButton>();
          }
      };
      

      及用法:

      ShapeVariant shape = GetShape();
      boost::apply_visitor(to_button(), shape);
      

      Demo

      【讨论】:

        【解决方案4】:

        如果您打算采用 OOP 方法,那么您几乎会被一种或另一种 RTTI 所困扰。但是,如果您真的想像标签所暗示的那样进行“通用编程”,那么就完全通用!非成员函数特化可以让你到达那里,例如:

        #include <vector>
        #include <stdio.h>
        // Shapes
        struct Circle { };
        struct Square { };
        
        // Buttons
        struct Button {
            virtual void click() { printf("Button clicked\n"); }
        };
        
        struct CircleButton : Button {
            void click() override { printf("CircleButton clicked\n"); }
        };
        
        struct SquareButton : Button {
            void click() override { printf("SquareButton clicked\n"); }
        };
        
        // Button creation
        
        template<typename TShape>
        auto getNewButton(const TShape& shape)
        {
            static_assert(false, "Don't know how to create a button for this type.");
        }
        
        template<>
        auto getNewButton(const Circle& shape)
        {
            return new CircleButton();
        }
        
        template<>
        auto getNewButton(const Square& shape)
        {
            return new SquareButton();
        }
        
        
        int main()
        {
            Circle circle;
            Square square;
        
            std::vector<Button*> buttons;
            buttons.push_back(getNewButton(circle));
            buttons.push_back(getNewButton(square));
        
            for (auto pButton : buttons)
                pButton->click();
        
            return 0;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-26
          • 2018-08-31
          • 1970-01-01
          • 2016-02-09
          • 2011-03-09
          • 1970-01-01
          相关资源
          最近更新 更多