2010-11-20 4 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. 希望する場合、私は空の関数は上記のように定義されている場合、コンパイラは(INPUT1〜INPUT3)私に参照されていないパラメータを警告し続けるベースまたは派生いずれかの空の関数を定義する必要があります。もちろん、私はそれをオフにすることができますが、ちょうどこのようにしないでください。

したがって、空の関数を定義せずに均一なインターフェイスを実現するために使用できるパターンはありますか?私は数日間このことについて考えてきました。それは不可能と思われる。すべてのオブジェクトを制御するためにBaseポインタ配列を使うことができるようにfunA()funB()とfunC()がインタフェースになければならないので、funB()とfunC()を何らかの方法で定義する必要があります。

感謝と幸せな感謝と、あなたのアイデアを共有してくれてありがとう。

solti

答えて

1

統一されたインターフェイスは良いことです。つまり、空のメソッドがある場合でも、すべてのメソッドをインターフェイスに実装する必要があります。この問題の設計パターンはありません。最初は本当の問題ではないからです。

これについては、Carのインターフェイスがあり、方法がAccelerate()Brake()であるとします。 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

こんにちは、お返事ありがとうございます。ブレーキなしの車であなたが何を意味するのか理解できます()。しかし、何らかのOEMソフトウェアを開発している場合は、同じライブラリレベルのコードを使用し、異なるAPレベルのコードを使用することもできます。ですから、私はインターフェースを変えずにAPでいくつかのコードを変更するだけでいいと思っています。だから、一部のクライアントにとって、私はBrakeなしで彼らに車を与えたい()。それはcar-> Brake()が決して必要でないことが保証されています。 (たぶんクライアントがクラッシュテストをしている)第二に、私はトリックを省略してパラメータ名を試して、それは動作します!どうもありがとうございます! – Solti

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

これはどちらか起動しますMethodAまたはMethodBの場合、ランタイムタイプはxです。実装されている訪問者は、多くの関数をオーバーロードしないようにできます。ビヘイビアが実装されていない場合は、基本クラスのアクションに戻ります。最終的に、あるクラスの訪問者にアクションを提供できない場合は、デフォルトでthrow "not implemented"になります。

正確であるとすれば、VisitorConstVisitorを区別する必要があります。すべてのacceptメソッドはconstになります。