【问题标题】:Design issue in C++: code reuse in interface centric designC++ 中的设计问题:以接口为中心的设计中的代码重用
【发布时间】:2012-04-10 02:44:27
【问题描述】:

该项目有一个目标:使用以界面为中心的设计。基本上,我们只声明公共纯函数的类,并让真正的类继承自它们。

现在出现一个问题——如何重用实用函数?

这是一个例子:

class InterF1 {
  public:
    int f1() = 0;
    int g1() = 0;   
}; 

class InterF2 {
  public:
    int f2() = 0;
}; 

class C1: public InterF1 {
  public:
    int f1();
    int g1();
};


class C2: public InterF1, InterF2 {
  public:
    int f1();
    int g1();
    int f2();
};

在实现 C1::f1() 和 C2::f1() 时,我发现这两个函数之间存在代码重复。我应该如何删除重复项?

以下是我的想法:

1) 我可以像这样向接口类添加保护:

class InterF1 {
  public:
    int f1() = 0;
    int g1() = 0;   
  protected:
    int util();
    int convenient_var_calculated_by_util;
}; 

在上述设计中,C1::f1() 和 C2::f2() 都可以调用 util()。

2) 我可以抽象出一个新接口:

class UtilI {
    public:
      int util(int in_var) = 0;
};

在本设计中,InterF1、C1和C2改为

class InterF1 {
  public:
    int f1(UtilI u) = 0;
    int g1() = 0;   
}; 

class C1: public InterF1 {
  public:
    int f1(UtilI u);
    int g1();
};


class C2: public InterF1, InterF2 {
  public:
    int f1(UtilI u);
    int g1();
    int f2();
};

和 C1::f1() 和 C2::f1() 都调用 UtilI API。

解决方案 2 似乎更符合我们的“以界面为中心”的目标,而且看起来确实更好。但我有一个担心,InterF1 和 InterF2 本身应该是比 UtilI 更高级别的抽象,它更多的是实现细节,我是否混合了 2 个级别?换句话说,如果以后实现发生变化,我将需要再次更新 f1() 和 f2() 签名,这听起来不对。

解决方案 1 似乎很方便,但对我来说它看起来不那么“纯粹”,我在这里是不是太教条了?

【问题讨论】:

    标签: c++ oop


    【解决方案1】:

    通常我会说您应该将通用实现放在基类中 - 但您需要基类是纯虚拟的。因此,我建议:

    1. 创建中间实现类

      如果每个想要实现InterF2 的类也想实现InterF1,这种方法是可以的。

      方法是实现

      class InterF1Impl: public InterF1 {
        public:
          int f1();
          int g1();
      }
      

      那么C1C2 都可以从InterF1Impl 派生而来,它实现了常见的f1()g1() 方法。

      但是,如果您有另一个类 C3 : public InterF2,它希望与 C2 共享 f2() 的实现,但又不想实现 InterF1,则此方法将无法很好地扩展。

      另一种(更好的)方法是

    2. 使用组合来包含实现类

      在这种方法中,如上所述实现InterF1Impl: public InterF1,但不是派生 C1: public InterF1Impl,而是让InterF1Impl 成为C1 类的一部分。

      class C1: public InterF1 {
        private:
          InterF1Impl * f1impl;
        public:
          int f1();
          int g1();
      }
      

      C1::f1()C1::g1() 方法只是从f1impl 调用相应的方法。

      这样,如果你需要f2()的通用实现,那么你也可以实现InterF2: public InterF2C2可以类似的方式实现:

      class C2: public InterF1, InterF2 {
        private:
          InterF1Impl * f1impl;
          InterF2Impl * f2impl;
        public:
          int f1(); /* calls f1impl->f1() */
          int g1(); /* calls f1impl->g1() */
          int f2(); /* calls f2impl->f2() */
      }
      

      如果您以后需要这样的类,另一个类可以只使用InterF2 的实现,而无需同时实现InterF1

    【讨论】:

    • 您所说的一个变体也将具有仅在 C1 和 C2 的实现中的类和/或一系列普通实用程序函数。因此它们包含在实现中,但从未暴露给公共接口。这似乎比在类中指向实用程序类要简单一些,后者仍然将它们作为实现的一部分“公开”。
    【解决方案2】:

    您的第二个解决方案比第一个更好,但我会采取不同的做法。

    首先,代码重用可以通过对象之间的继承或组合关系来实现。在这种情况下,组合方法更有意义,如下所述。

    您的类的用户可能不关心util() 方法的实现,甚至它的存在。因此,拥有UtilI 接口并使其成为公共 API 的一部分似乎是不必要的。由于实用程序逻辑是您的实现的一部分,而不是接口的一部分,因此此类应仅用于 C1 和 C2 类的实现。如果你这样做,你可能不需要UtilI 接口,并且可以只用具体类Util 替换。您可能可以使此类无状态,因此它只有静态方法。如果不是,那么它可能是单例,或者您的类可以创建自己的实例(取决于您的需要)。

    以界面为中心的设计是一个很好的目标,但重要的是您只公开应该公开的内容。这是设计任何 API 的重要部分,它应该是完整的最小的

    【讨论】:

      猜你喜欢
      • 2014-03-21
      • 2018-09-24
      • 1970-01-01
      • 2018-01-23
      • 2011-03-21
      • 2020-10-12
      • 2015-10-29
      • 2011-06-29
      • 1970-01-01
      相关资源
      最近更新 更多