2010-11-20 46 views
0

我正在爲我的項目設計一個接口,並好奇這個想法是否可以實現。 這裏是情況, 在運行時,我想使用一個基類指針數組來發出命令給不同的派生對象。不同的派生對象有不同的實現(虛函數)。我的問題是:如果這些對象獲得了不同的接口支持級別,我該如何避免寫一個空函數?設計模式:部分支持派生類的統一接口

例如,(我當前的代碼)

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()。 問題是:

  1. 我必須要麼在基地限定一個空的功能,或者如果一個統一的接口期望
  2. 如果空白函數像上面定義的派生,編譯器保持警告我未引用的參數(輸入1〜輸入3) 。當然,我可以關閉它,但只是不喜歡這種方式。

那麼,有沒有什麼模式可以用來實現統一接口而不需要定義空函數?我一直在想這個幾天。這似乎不可能。因爲funA()funB()和funC()必須在接口中,所以我可以使用Base指針數組來控制所有對象,這意味着在Derived1中,必須以某種方式定義funB()和funC()。

非常感謝,感謝您分享您的想法。

Solti

回答

1

統一接口是一件好事。這意味着您必須在界面中實現所有方法,即使這意味着您將有空方法。這個問題沒有設計模式,因爲它首先不是一個真正的問題。

想一想:說你有一個Car接口,方法Accelerate()Brake()。來自Car的類必須實現所有方法。你想從Car衍生的對象實施Accelerate()方法,但不是Brake()方法?這將是一個驚人的不安全Car

OOP環境中的接口必須有雙方遵守的定義良好的合同。在C++中,通過要求所有純虛擬實現在派生類中,這在一定程度上得到強化。試圖用未實現的虛擬方法實例化一個類會導致編譯錯誤,假設不使用愚蠢的技巧來繞過它。

您反對創建空方法,因爲它們會導致編譯器警告。在你的情況,只是省略參數名:

void funC(int) // Note lack of parameter name 
{ 
} 

或評論出了名的:

void funC(int /*input3*/) 
{ 
} 

甚至by using templates!

template<class T> void ignore(const T&) { } 

//... 

void funC(int input3) 
{ 
    ignore(input3); 
} 
+0

嗨,謝謝你的回覆。我可以理解沒有Brake()的汽車是什麼意思。但是,如果我們正在開發某種OEM軟件,則可能需要使用相同的庫級代碼,但使用不同的AP級代碼。所以我希望保持接口不變,只修改AP中的一些代碼。所以,對於一些客戶,我想給他們一輛沒有Brake()的汽車。並且保證car-> Brake()從不需要。 (也許客戶端正在做崩潰測試)其次,我嘗試了參數名稱省略技巧,它的工作原理!非常感謝! – Solti 2010-11-22 20:08:43

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(); 
} 
0

如果你真的需要非統一接口(前想想),也許Visitor模式是值得一試:

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

這將調用任MethodAMethodB,具體取決於x的運行時間類型。實現的訪問者允許您不要重載許多函數,並且如果行爲未實現,則會退回到基類的操作。最終,如果您未能在某個班級的訪問者中提供操作,它將默認爲throw "not implemented"

Const正確性可能會強制您區分VisitorConstVisitor,對於這些方法,所有accept方法都是const。