隐式接口与显式接口

发布于 2025-01-04 01:52:38 字数 2567 浏览 1 评论 0原文

在下面的示例中,使用隐式接口(情况 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;
}

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

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;
}

情况 3:模板类的模板type 提供了一个隐式接口(这一次,不是从 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 的子级(并实现另一方面,情况 2 和 3 将采用具有 doSomethingCool() 函数的任何

。的代码总是很好地子类化 CoolClass,那么情况 1 就具有直观意义,因为 CoolClassUser 总是期望实现 CoolClass。但假设这段代码是是 API 框架的一部分,因此我无法预测用户是否想要继承 CoolClass 或推出自己的具有 doSomethingCool() 函数的

类一些相关帖子:

https://stackoverflow.com/a/7264550/635125

https://stackoverflow.com/a/7264689/635125

https://stackoverflow.com/a/8009872/635125

What are the pros/cons of using implicit interfaces (Cases 2 and 3; templates) vs using explicit interfaces (Case 1; pointer to abstract class) in the following example?

Code that doesn't change:

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 */ }
};

Case 1: A non-templated class that takes a base-class pointer which provides an explicit interface:

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;
}

Case 2: A templated class whose template type provides an implicit interface:

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;
}

Case 3: A templated class whose template type provides an implicit interface (this time, not deriving from 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;
}

Case 1 requires that the object being passed in to useCoolClass() be a child of CoolClass (and implement worthless()). Cases 2 and 3, on the other hand, will take any class that has a doSomethingCool() function.

If users of the code were always fine subclassing CoolClass, then Case 1 makes intuitive sense, since the CoolClassUser would always be expecting an implementation of a CoolClass. But assume this code will be part of an API framework, so I cannot predict if users will want to subclass CoolClass or roll their own class that has a doSomethingCool() function.

Some related posts:

https://stackoverflow.com/a/7264550/635125

https://stackoverflow.com/a/7264689/635125

https://stackoverflow.com/a/8009872/635125

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

痴情换悲伤 2025-01-11 01:52:38

我想到的一些考虑因素解释了为什么您可能更喜欢案例 1:

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

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

  • 如果您后来认为 worthless() 现在有价值,并开始使用它,那么在情况 2 中,您将收到不值钱的类的编译时错误实施的。在情况 1 中,没有任何东西会提醒您真正实现这些函数,除非您运气好的话可能会出现运行时错误。
  • Case2/3 可能具有稍微更好的性能,但代价是代码大小更大。

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

Some considerations that came to my mind for why you could prefer the Case 1:

  • If CoolClass is not a pure interface, i.e. part of implementation is also inherited (though you might provide it for Case 2/3 too, e.g. in the form of a base class);
  • if there are reasons to have CoolClassUser implemented in a binary rather than a header (and that's not only protection but could also be code size, control of resources, centralized error handling etc.);
  • if you want to store the pointers and use it later, then also Case 1 seems better: (a) it's easier to keep them all in the same container, and (b) you would need to store the actual data type as well, and for Case 2/3 the solution that comes to mind is to convert it to "explicit" interface (i.e. Case 1) with help of a template wrapper.

Reasons why Case 2/3 might be preferrable:

  • if you later decide that worthless() is now worth something, and start using it, in Case 2 you will get compile-time errors for classes where it's not implemented. In Case 1, nothing will remind you to implement these functions for real, except maybe run-time errors if you are (un)lucky.
  • Case2/3 might have slightly better performance, though at the expense of bigger code size.

In some cases, it might be purely the matter of personal preferences, either yours or your users.

ˇ宁静的妩媚 2025-01-11 01:52:38

请记住,在情况 #2 和 #3 中,您依赖于模板参数,这意味着编码器在调用时必须使用正确的类型正确实例化模板参数。根据函数的使用方式,这可能会产生一些问题,您希望为用户创建一个抽象接口,而不必担心传递的对象的类型......即“句柄”或某些other 指向派生对象的指针,该派生对象使用多态性将对象从一个 API 函数传递到另一个 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* 的函数模板,只会导致用户键入更多样板代码。

Keep in mind that in cases #2 and #3, you're depending on template parameters, which means that the coder at the time of the call will have to properly instantiate the template argument with the correct type. Depending on how the functions will be used, that could create some issues where you want to create an abstract interface for the user without them having to worry about the type of the object being passed around ... i.e., a "handle" or some other pointer to a derived object that is using polymorphism to pass an object around from one API function to another. For instance:

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

Now, your API framework can pass an object back to the user of your code, and they don't need to know what that object is ... they only need to know that it describes some type of interface, which you can of course publicly expose in a header somewhere. But they won't have to know anything about the "guts" of the object you've passed back to them. You can give them a pointer to some derived type that you control the implementation of. You would only need to provide templates for the most generic types of functions in your API. Otherwise having to instantiate a template for functions that are only designed for taking a abstract_base_class* just makes for more boilerplate code for the user to type.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文