在以下示例中,使用隐式接口(案例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的子对象(并实现 worthless())。另一方面,情况2和3将采用具有 doSomethingCool()功能的任何类。
如果代码的用户总是很好地继承了 CoolClass ,那么Case 1就具有直观意义,因为 CoolClassUser 总是希望实现 CoolClass 的。但是假设这段代码将成为API框架的一部分,所以我无法预测用户是否想要继承 CoolClass 或者推送他们自己的具有 doSomethingCool()功能的类。
一些相关帖子:
https://stackoverflow.com/a/7264550/635125
答案 0 :(得分:3)
我想到了为什么你更喜欢案例1的一些注意事项:
CoolClass
不是纯接口,即实现的一部分也是继承的(尽管您也可以为案例2/3提供它,例如以基类的形式); CoolClassUser
(并且不仅保护,而且还可以是代码大小,资源控制,集中式错误处理等); 案例2/3可能更适合的原因:
worthless()
现在值得一试,并开始使用它,那么在案例2中,您将获得未实现的类的编译时错误。在案例1中,没有任何内容会提醒您实现这些功能,除非您(幸运)幸运,否则可能会出现运行时错误。在某些情况下,无论是您的用户还是您的用户,这可能完全取决于个人偏好。
答案 1 :(得分:1)
请记住,在情况#2和#3中,您依赖于模板参数,这意味着在调用时编码器必须使用正确的类型正确地实例化模板参数。根据函数的使用方式,这可能会产生一些问题,您希望为用户创建一个抽象接口,而不必担心传递的对象的类型...即“句柄”或某些指向派生对象的其他指针,该派生对象使用多态将一个对象从一个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*
设计的函数实例化模板,这样才能为用户输入更多的样板代码。