隐式与显式接口

在以下示例中,使用隐式接口(情况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:一个模板类,其模板类型提供了一个隐式接口(这次不是从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,那么Case 1就具有直观的意义,因为CoolClassUser总是期望实现一个CoolClass。 但是,假设这些代码将成为API框架的一部分,所以我无法预测用户是否想要子类化CoolClass或者滚动他们自己的具有doSomethingCool()函数的类。

一些相关的帖子:

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

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

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


在我看来,为什么你更喜欢案例1的一些考虑因素:

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

  • 如果你后来认为worthless()现在是值得的东西,并开始使用它,在情况2中,你将得到编译时错误的类没有实现。 在案例1中,没有任何东西会提醒您实现这些功能,除非运行时错误(除非您是幸运的)。
  • Case2 / 3虽然代价较大,但性能稍微好一些。
  • 在某些情况下,它可能纯粹是个人喜好的问题,无论是您的用户还是您的用户。


    请记住,在#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*函数的模板,只是为用户输入更多的样板代码。

    链接地址: http://www.djcxy.com/p/57565.html

    上一篇: implicit vs explicit interfaces

    下一篇: Implementing numbers