【问题标题】:Classes design with virtual methods使用虚拟方法进行类设计
【发布时间】:2016-05-05 08:12:26
【问题描述】:

如何使用类解决此类问题并尽可能少地键入代码? 这是我所拥有的

一切的基本接口

    class IWindow
    {
    public: 
        virtual void Refresh() = 0;
// another 100 virtual methods
// ...
    };

这个接口在一个不知道具体实现的库中使用。

这里是具体实现的一个版本

    class ConcreteWindow : public IWindow
    {
    public:
        void Refresh() override {}
/// the other 100 overridden methods 
    };

现在我们有了另一个接口,它添加了一些额外的方法并在该库中使用。

class IDBDetail : public IWindow
{
public:
    virtual void DoDetail() = 0;

};

这是主要问题,当我们为其创建具体实现时

class IGDBDetailWrapper : public IDBDetail, public ConcreteWindow
{
public :
    void DoDetail() {}
};

当然,具体类IGDBDetailWrapper 也是抽象类,因为它没有实现这 100 种方法,但我不想那样做,我只想重用 ConcreteWindow 的实现,它们是所有人都使用相同的窗口句柄,但这当然不会编译。

我可以将这 100 个方法从 ConcreteWindow 复制/粘贴到 IGDBDetailWrapper,但这有点过头了,因为我可能还有另外 10 个这样的新接口和具体实现。

我可以在这里使用什么其他模式来帮助解决问题,而不是一次又一次地重新实现这 100 种方法?

谢谢

【问题讨论】:

  • 你只是做错了。当一个纯接口具有多个功能时,它会变得非常乏味。但更糟糕的是,调试起来非常困难。只是不要,“窗口”需要一个基类才能实用。一定要看看其他人是如何做到这一点的,周围有很多很多的 GUI 包装库。
  • 为什么不直接从 ConcreteWindow 继承?
  • 这只是糟糕的库设计,因为基类中的 100 个方法太可怕了,IDBDetailIWindow 之间的继承关系看起来不对。你不会找到干净的解决方案;该库的任何客户都必须使用一种或另一种解决方法。
  • P.S.:这个库很可能是由于试图直接从一些 Java 代码库转换类层次结构而产生的。但是在 Java 中,接口继承是隐式的virtual。 IOW,interface Base { void f(); } interface D1 extends Base {} interface D2 extends Base {} class D implements D1, D2 { @Override public void f() {}} 将起作用,但 C++ 等效项必须在 D1D2 的类定义中显式指定 virtual 关键字。

标签: c++ oop


【解决方案1】:

您的设计遇到了diamond problem

现在我们有了另一个接口,它添加了一些额外的方法和 也在那个库中使用。

 class IDBDetail : public IWindow { 
  public:
     virtual void DoDetail() = 0;

};

从 IDBDetail 接口的描述看来,IDBDetail 不应该从 IWindow 继承。如果它只是添加附加功能,那么 IDBDetail 不必是 IWindow。它只需要了解 IWindow。例如,为了让猴子做一些特殊的事情,训练师不必是猴子。

Decorator pattern 可能就是您要找的。

【讨论】:

  • 它不起作用,因为在该库中,有一个方法使用指向 IDBDetail 的指针来访问 IWindow 中的方法。不幸的是,我无法更改该库。
  • @Eugen 好的。但是,如果您在问题中执行其中一种替代方案,您仍然不能使用当前的继承模型。因为钻石问题
【解决方案2】:

首先,如果您使用的是 Visual Studio,那么有一些重构工具可以帮助您将原本乏味的任务自动化,其次:

对我来说这样做毫无意义:

class IDBDetail : public IWindow
{
public:
    virtual void DoDetail() = 0;

};

我会这样做

class IDBDetail
{
public:
    virtual void DoDetail() = 0;

};

应该使用接口来抽象出职责,因此将已经有数百个方法和其他方法的接口弄得杂乱无章是糟糕设计的表现。

但是,您可以一次性利用组合,因此您可以一次性创建一个可以为您解决问题的类,然后您可以在以后重用该类

class IDBDetailWithConcreteWindow: public IDBDetail{

    IWindow * concreteWindow;
public:
    IDBDetailWithConcreteWindow(IWindow * window){
        concreteWindow = window;
    }

    void Refresh() override{
        concreteWindow->Refresh();
    }
}

最后,在任何派生类中,您只需实现 IDBDetail 中的方法

IGDBDetailWrapper: public IDBDetailWithConcreteWindow{
public:

   void DoDetail() override { }
}

此解决方案的优势在于,如果您有外部约束(例如设计不佳的预先存在的代码库),您仍然可以使用它,而如果您无法更改 IDBDetail 接口,则上层解决方案将无法工作。

【讨论】:

    【解决方案3】:

    @bashrc 是对的,但是用虚拟继承应该可以解决问题:

    class ConcreteWindow : public virtual IWindow {...}
    
    class IDBDetail : public virtual IWindow {...}
    

    这篇关于virtual inheritance 的维基百科文章也说明了解决方案。

    【讨论】:

      【解决方案4】:

      您可以使用虚拟继承。如果我们忽略IDBDetail是否继承IWindow的事实,我们可以使用虚拟继承来解决当前架构的问题:

          class IWindow
          {
          public: 
              virtual void Refresh() = 0;
      // another 100 virtual methods
      // ...
          };
      
          class ConcreteWindow : virtual public IWindow
          {
          public:
              void Refresh() override {}
      /// the other 100 overridden methods 
          };
      
      class IDBDetail : virtual public IWindow
      {
      public:
          virtual void DoDetail() = 0;
      
      };
      
      class IGDBDetailWrapper : public IDBDetail, public ConcreteWindow
      {
      public :
          void DoDetail() {}
      };
      

      现在编译器将使用来自ConcreteWindow 的 101 抽象方法的实现

      【讨论】:

        【解决方案5】:

        你必须重写抽象类中的所有方法,没有别的办法。实际上,您不应该在这里创建一个包含 100 个方法的抽象类,仅此而已。也许你可以把它分成一些更小的抽象类?但是,在这种情况下,IDBDetail 不应该在 IWindow 之后继承,而 IGBDDetailWrapper 也不应该在 IWindow 之后继承 - 我们就在这里。

        【讨论】:

        • 也许分成更小的班级可能会有所帮助。我必须检查一下。
        【解决方案6】:

        它不会解决你的问题,但至少你可以自己重定向执行:

        class IGDBDetailWrapper : public IDBDetail, public ConcreteWindow
        {
        public:
            virtual void DoDetail() override { /*work here*/ }
        
            virtual void Refresh() override { ConcreteWindow::Refresh(); }
            //another 100 methods
        };
        

        您可以像编译器#DEFINE 一样制作此类重定向块,并根据需要重复多次。

        【讨论】:

        • 正如@DarioOO 所说,您可以使用Visual Studio refactoring tools 自动创建重定向。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-10-21
        • 2011-09-30
        • 1970-01-01
        • 1970-01-01
        • 2015-06-13
        • 2013-12-03
        • 1970-01-01
        相关资源
        最近更新 更多