隐式界面 vs 显式界面

7

在以下示例中,使用隐式接口(情况2和3;模板)与使用显式接口(情况1;指向抽象类的指针)有何优缺点?

不需要更改的代码:

class CoolClass
{
public:
  virtual void doSomethingCool() = 0;
  virtual void worthless() = 0;
};

class CoolA : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that an A would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

class CoolB : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that a B would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

情况1:一个非模板类,它接受一个提供显式接口的基类指针:

class CoolClassUser
{
public:  
  void useCoolClass(CoolClass * coolClass)
  { coolClass.doSomethingCool(); }
};

int main()
{
  CoolClass * c1 = new CoolA;
  CoolClass * c2 = new CoolB;

  CoolClassUser user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

情况二:模板类的模板类型提供隐式接口:

template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  CoolClass * c1 = new CoolA;
  CoolClass * c2 = new CoolB;

  CoolClassUser<CoolClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

第三种情况:一个模板类,其模板类型提供了一个隐式接口(这次不是从CoolClass派生的):
class RandomClass
{
public:
  void doSomethingCool()
  { /* Do cool stuff that a RandomClass would do */ }

  // I don't have to implement worthless()! Na na na na na!
};

template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  RandomClass * c1 = new RandomClass;
  RandomClass * c2 = new RandomClass;

  CoolClassUser<RandomClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

案例1要求传递给useCoolClass()的对象必须是CoolClass的子类(并实现worthless())。相反,案例2和3将接受任何具有doSomethingCool()函数的类。
如果代码的用户总是可以接受子类化CoolClass,那么案例1就很直观了,因为CoolClassUser总是期望CoolClass的实现。但是假设这段代码将成为API框架的一部分,因此我无法预测用户是否希望子类化CoolClass或自己编写具有doSomethingCool()函数的类。
一些相关的帖子: https://dev59.com/62w05IYBdhLWcg3wZA8C#7264550 https://dev59.com/62w05IYBdhLWcg3wZA8C#7264689 https://dev59.com/amsz5IYBdhLWcg3wR1oc#8009872

你的Case 1和Case 2无法编译。指针初始化方向错误。 - Phil Miller
2个回答

3

以下是我想到的几个理由,为什么你可能更喜欢使用情况1:

  • 如果 CoolClass 不是一个纯接口,即部分实现也被继承(虽然您也可以提供基类的形式来支持情况2/3);
  • 如果有理由在二进制文件中实现 CoolClassUser 而不是头文件(这不仅涉及保护,还可能涉及代码大小、资源控制、集中的错误处理等);
  • 如果您想存储指针并稍后使用它们,那么情况1似乎更好:(a) 将它们全部放在同一个容器中更容易,(b) 您还需要存储实际的数据类型,对于情况2/3,我的解决方案是使用模板包装器将其转换为“显式”接口(即情况1)。

以下是情况2/3可能更可取的原因:

  • 如果以后您决定 worthless() 有价值了,并开始使用它,则在情况2中,未实现该函数的类会在编译时产生错误。在情况1中,除非您很幸运或者不幸地遇到运行时错误,否则没有任何东西会提醒您真正实现这些函数。
  • 情况2/3可能具有略微更好的性能,但代价是更大的代码大小。

在某些情况下,这可能纯粹是个人偏好的问题,无论是您还是您的用户。


1
请记住,在第2和第3种情况下,您依赖于模板参数,这意味着在调用时,编码人员必须正确实例化模板参数以使用正确的类型。根据函数的使用方式,这可能会创建一些问题,例如,您希望为用户创建一个抽象接口,而不必担心传递的对象类型...即一个“句柄”或其他指向派生对象的指针,该对象使用多态性从一个API函数传递到另一个函数。例如:
class abstract_base_class;

abtract_base_class* get_handle();
void do_something_with_handle(abstract_base_class* handle);
void do_something_else_with_handle(abstract_base_class* handle);
//... more API functions

现在,您的API框架可以将一个对象传回给您代码的用户,而他们不需要知道该对象是什么...他们只需要知道它描述了某种类型的接口,您当然可以在某个头文件中公开暴露它。但是他们不需要知道您传回给他们的对象的“内部机制”。您可以向他们提供指向某些派生类型的指针,您可以控制其实现。您只需要为API中最通用类型的函数提供模板即可。否则,为仅设计用于获取abstract_base_class*的函数实例化模板只会为用户生成更多样板代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接