【问题标题】:Design of a Wrapper Class in C++C++中包装类的设计
【发布时间】:2017-04-24 09:48:33
【问题描述】:

我必须使用界面非常笨拙的旧类。由于我无法更改它并且依赖它,我想构建一个包装器,提供一个干净的界面。假设我有一个类 ClumsyClass。基本上,我有三种方法:

1.参考成员

    Class Wrapper {
      public:
        Wrapper (ClumsyClass& clumsyClass)
          : m_clumsyClass(clumsyClass)
        { }

        int getSmth() {
          return m_clumsyClass.getSmth();
        }

        private:
          ClumsyClass& m_clumsyClass;
}

2。指针成员

    Class Wrapper {
      public:
        Wrapper (ClumsyClass* clumsyClass)
          : m_clumsyClass(clumsyClass)
        { }

        int getSmth() {
          return m_clumsyClass->getSmth();
        }

        private:
          ClumsyClass* m_clumsyClass;
}

3.继承

    Class Wrapper : public ClumsyClass {
    ...
}

哪种方法是实现包装器的“最干净”的方式?我更喜欢第三种,但是当我已经有一个 ClumsyClass 对象然后创建一个 Wrapper 对象(复制构造函数)时,将需要更多内存(因为在我的情况下需要原始类的实例)。

【问题讨论】:

  • 您想应用Pointer to Implementation (PIMPL) 成语。
  • 4.如果不将实例传递给构造函数,就不能将实例作为成员吗?
  • 您的包装类是否应该拥有现有 ClumsyClass 对象的所有权?如果是,则 std::move ClumsyClass 对象到你的包装类中。
  • 您可能想申请facade pattern
  • 切勿将参考成员用于任何目的,句号。只使用公共继承来表达is-a relationship。我猜这会杀死三分之二。

标签: c++ design-patterns wrapper


【解决方案1】:

我会避免使用 3,因为它无法封装 ClumsyClassWrapper 的用户可以有意或无意地直接访问ClumsyClass 的“笨拙”界面,这是您要避免的。更喜欢组合而不是继承。

1. 和 2. 之间的差异很小。使用引用成员会降低包装器的灵活性。该类不可分配,您不能重新安装引用并将其替换为 ClumsyClass 的不同实例,并且成员不能为空。根据您的要求,这些可能是好是坏。

但正如 cmets 中所述,默认选择可能应该是让 ClumsyClass 作为 Wrapper 的按值成员:

class Wrapper {
  public:
    // possible constructors
    //Wrapper(const ClumsyClass& cc) : m_clumsyClass(cc) {} // copy 
    //Wrapper(ClumsyClass&& cc) : m_clumsyClass(std::move(cc)) {}  // move 
    int getSmth() { return m_clumsyClass.getSmth(); }

  private:
    ClumsyClass m_clumsyClass;
};

在您的特定用例中这可能不可行或不可取的原因有很多,然后您可以退回到选项 1 或 2。决定主要取决于所有权。 Wrapper 应该“拥有”ClumsyClass 还是 ClumsyClass 的实例的生命周期超出 Wrapper 的生命周期?

使用直接成员的一个潜在缺点是您不能再将ClumsyClass 的实现隐藏在前向声明后面,因此您会丢失ClumsyClass 的一些封装。值得注意的是,解决此问题的一种方法是提取Wrapper 继承自的抽象基类“接口”。比如:

class IWrapper {
  public:
    virtual ~IWrapper() {}
    virtual int getSmth() = 0;
};

这可能会提供额外的好处,例如可测试性。

【讨论】:

    【解决方案2】:

    从您提到的限制看来,这可能是一种可行的方法:

    用你喜欢的方法创建一个接口

    struct IMyMethods 
    {
      virtual int getOther() = 0;
      ...
      virtual ~IMyMethods() {};
    };
    

    现在创建一个新类,该类派生自您的类和要在添加接口实现的类上使用的接口

    class MyAgileClass : public ClumsyClass, IMyMethods
    {
    public:
       MyAgileClass(/*same args as for ClumsyClass*/)
       {}
       ~MyAgileClass() 
       {}
    
       virtual int getOther() 
       {
         return getSmth(); // clumsy class function
       }
       ...
    };
    

    您现在可以直接或通过简化界面访问 ClumsyClass

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-15
      • 2014-12-28
      • 2011-02-14
      • 1970-01-01
      • 2015-11-26
      • 2011-01-28
      • 1970-01-01
      • 2019-04-18
      相关资源
      最近更新 更多