隐式与显式接口
在以下示例中,使用隐式接口(情况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
在二进制文件而不是头文件中实现(这不仅是保护,还可以是代码大小,资源控制,集中式错误处理等)。 案例2/3可能更可取的原因:
worthless()
现在是值得的东西,并开始使用它,在情况2中,你将得到编译时错误的类没有实现。 在案例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*
函数的模板,只是为用户输入更多的样板代码。
上一篇: implicit vs explicit interfaces
下一篇: Implementing numbers