我正在为我的项目设计一个界面,并且好奇这个想法是否可以实现。 这是情况, 在运行时,我想使用一个基类指针数组来向不同的派生对象发出命令。不同的派生对象获得了不同的实现(虚函数)。我的问题是:如果这些对象得到不同的接口支持级别,我怎么能避免写一个空函数?
例如,(我当前的代码)
Class Base { //this is the main interface
public:
virtual void basicFun() = 0;
virtual void funA(int input1) {return;}
virtual void funB(int input2) {return;}
virtual void funC(int input3) {return;}
}
Class Derived1 : public Base { //this class support only funA()
public:
void basicFun() {//....}
void funA(int input1) {//do something}
}
Class Derived2 : public Base { //this class support both funA() funB()
public:
void basicFun() {//....}
void funA(int input1) {//do something}
void funB(int input2) {//do something}
}
Class Derived3 : public Base { //this class support all
public:
void basicFun() {//....}
void funA(int input1) {//do something}
void funB(int input2) {//do something}
void funC(int input3) {//do something}
}
假设:对于某个对象,永远不会调用不支持的函数。如果basePtr指向的对象是Derived1或Derived2,则永远不会调用BasePtr-> funC()。 问题是:
那么,是否有任何模式可用于实现统一接口而无需定义空函数?我已经考虑了几天了。这似乎不可能。因为funA()funB()和funC()必须在接口中,所以我可以使用Base指针数组来控制所有对象,这意味着在Derived1中,必须以某种方式定义funB()和funC()。
谢谢,感恩节快乐,感谢您分享您的想法。
索尔蒂
答案 0 :(得分:1)
统一接口是一件好事。这意味着您必须在接口中实现所有方法,即使这意味着您将拥有空方法。这个问题没有设计模式,因为它首先不是一个真正的问题。
暂时考虑一下:假设您有Car
界面,方法Accelerate()
和Brake()
。派生自Car
的类必须实现所有方法。您是否希望从Car
派生的对象实现Accelerate()
方法而不是Brake()
方法?这将是一个非常不安全的Car
!
OOP上下文中的接口必须具有双方都遵守的明确定义的合同。在C ++中,通过要求在派生类中实现所有纯虚拟,在某种程度上强制执行此操作。尝试使用未实现的虚方法实例化一个类会导致编译错误,假设有人不使用愚蠢的技巧来解决它。
您反对创建空方法,因为它们会导致编译器警告。在您的情况下,只需省略参数名称:
void funC(int) // Note lack of parameter name
{
}
或者说出这个名字:
void funC(int /*input3*/)
{
}
template<class T> void ignore( const T& ) { }
//...
void funC(int input3)
{
ignore(input3);
}
答案 1 :(得分:0)
这就是我要做的事情:使用所有纯虚方法的空默认实现创建一个帮助器基类。然后,您可以从此基类而不是主接口派生,然后选择要覆盖的方法。
// main interface, everything pure virtual
struct IBase
{
virtual ~IBase() {}
virtual void basicFun() = 0;
virtual void funA(int input1) = 0;
virtual void funB(int input2) = 0;
virtual void funC(int input3) = 0;
};
// helper base class with default implementations (empty)
class Base : public IBase
{
void basicFun() {}
void funA(int input1) {}
void funB(int input2) {}
void funC(int input3) {}
};
class Derived1 : public Base { //this class support only funA()
void funA(int input1) {//do something}
};
class Derived2 : public Base { //this class support both funA() funB()
void funA(int input1) {//do something}
void funB(int input2) {//do something}
};
class Derived3 : public IBase { //this class support all
void basicFun() {//....}
void funA(int input1) {//do something}
void funB(int input2) {//do something}
void funC(int input3) {//do something}
};
int main()
{
// I always program to the interface
IBase& b1 = Derived1(); b1.basicFun(); b1.funA(); b1.funB(); b1.funC();
IBase& b2 = Derived2(); b2.basicFun(); b2.funA(); b2.funB(); b2.funC();
IBase& b3 = Derived3(); b3.basicFun(); b3.funA(); b3.funB(); b3.funC();
}
答案 2 :(得分:0)
如果你真的需要非统一的界面(以前考虑一下),也许访客模式值得一试:
struct Visitor;
struct Base
{
virtual ~Base() {}
virtual void accept(Visitor& v) { v.visit(*this); }
};
struct InterfaceA : Base
{
void accept(Visitor& v) { v.visit(*this); }
virtual void MethodA() = 0;
};
struct InterfaceB : Base
{
void accept(Visitor& v) { v.visit(*this); }
virtual void MethodB() = 0;
};
struct InterfaceA2 : InterfaceA
{
void accept(Visitor& v) { v.visit(*this); }
void MethodA(); // Override, eg. in terms of MethodC
virtual void MethodC() = 0;
};
// Provide sensible default behavior. Note that the visitor class must be
// aware of the whole hierarchy of interfaces
struct Visitor
{
virtual ~Visitor() {}
virtual void visit(Base& b) { throw "not implemented"; }
virtual void visit(InterfaceA& x) { this->visit(static_cast<Base&>(x)); }
virtual void visit(InterfaceA2& x) { this->visit(static_cast<InterfaceA&>(x)); }
virtual void visit(InterfaceB& x) { this->visit(static_cast<Base&>(x)); }
};
// Concrete visitor: you don't have to override all the functions. The unimplemented
// ones will default to what you expect.
struct MyAction : Visitor
{
void visit(InterfaceA& x)
{
x.MethodA();
}
void visit(InterfaceB& x)
{
x.methodB();
}
};
用法:
Base* x = getSomeConcreteObject();
MyAction f;
x->accept(f);
这将调用MethodA
或MethodB
,具体取决于x
的运行时类型。实现的访问者允许您不重载许多函数,如果未实现该行为,则回退到基类的操作。最后,如果您未能在某个类的访问者中提供操作,则默认为throw "not implemented"
。
Const正确性可能会迫使您区分Visitor
和ConstVisitor
,对于他们来说,所有accept
方法都是const。