C++中的设计问题:以界面为中心的设计中的代码重用

Design issue in C++: code reuse in interface centric design

本文关键字:代码 为中心 问题 C++ 界面      更新时间:2023-10-16

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

现在出现了一个问题 - 如何重用实用程序函数?

下面是一个示例:

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 看起来确实很方便,但对我来说看起来不那么"纯粹",我在这里是不是太教条了?

通常我会

说你应该把公共实现放在基类中 - 但你要求基类是纯虚拟的。 因此,我建议

  1. 创建中间实现类

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

    方法是实施

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

    然后,C1C2都可以从实现常见f1()g1()方法的InterF1Impl派生。

    但是,如果您有另一个类 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如果你以后需要这样的类。

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

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

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

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

相关文章: