2012-01-25 11 views
2

のは私が改善する機会を持って、次のレガシーコードを持っているとしましょう:ベストプラクティス

class BaseInterfaceClass 
{ 

#if(HAS_FEATURE_A == 1) 
    void doFeatureA1() = 0; 
    void doFeatureA2() = 0; 
#endif 

#if(HAS_FEATURE_B == 1) 
    void doFeatureB1() = 0; 
    void doFeatureB2() = 0; 
    void doFeatureB3() = 0; 
#endif 

#if(HAS_FEATURE_C == 1) 
    void doFeatureC1() = 0; 
#endif 

    void doBaseFeature1() = 0; 
    void doBaseFeature2() = 0; 
}; 

は、コンパイル時にインタフェースを定義する#ifdef法を廃止するが方法です。新しいデザインのランタイムサイズを増やさずにつまり、HAS_FEATURE_Aでコンパイルされた派生クラスには、HAS_FEATURE_BまたはHAS_FEATURE_Cのコードは含まれません(コードはリンクされていますが、ランタイムチェックのために実際には使用されません)。

いくつかの注意事項

  1. 機能フラグは相互に排他的ではありません。それらの任意の組み合わせを定義することができます。

  2. 私が知覚しているように、各機能のサブクラスを定義し、複数の継承を使用して目的のインターフェイスをまとめても十分ではありません。派生クラスは、コンパイル時にフィーチャの任意の組み合わせを実装できなければならず、そのコンパイルに対して定義されていないフィーチャは含まれていなければならないことに注意してください。

  3. コードを変更するための政治的コストは、すべての指令を取り除くことができなければならない、あるいはまったく実行しないといけないことを意味します。言い換えれば、基本クラスをもっときれいにするために定義を移動するだけでは不十分です。

  4. 任意のブルートフォースアプローチが非現実的であるような機能の組み合わせが十分にあります。私は何千というサブクラスを作るつもりはない。

  5. 私はテンプレートを知らないが、それが鍵かどうかを知りたい。

+1

なぜ多重継承はありませんか?それは問題を非常にエレガントに解決するでしょう。 – Xeo

+0

私はそう多くの厄介な方法で壊れていることが分かります。 –

答えて

2

方法があります。しかし、彼らは本当に便利ですか?

多くのライブラリは、追加のオプトイン機能を提案するために、条件付きコンパイルを使用します(#if)。それは長い間確立された習慣です。

あなたはmix-insを使用してこれを「取り除く」ことができるかもしれませんが、結果としてコードがより読みやすくなるとは確信していません。それどころか、あなたのコードを使っている人は、以前は分かりやすいときに何が起こっているのかを今理解する必要があります。

一方、私たちはインターフェイスについて話しているので、簡単にを分割することができます。複数のコンポーネントにインターフェイスを分けて(可能であれば)、それぞれ複数の機能を提供します。あなたがクラスの複雑さについてより多くの知識を必要とするかどうかを知っているが、それはあまりにも神の目的に似ているように見える。

1

この場合、プリプロセッサは絶対に使用しないでください。

フィーチャを別個のインターフェイスに分けることができ、派生クラスが実装するフィーチャ(したがってそのインターフェイス)を厳選して選択することができます。

次にあなたが機能に到達するために別の検索インターフェースを持っている必要があります。この場合

class IFeature 
{ 
}; 

class IFeatureA : public IFeature 
{ 
    virtual void DoSomething() = 0; 
}; 

class IFeatureB : public IFeature 
{ 
    virtual void DoSomethingElse() = 0; 
}; 

class IFullComponent 
{ 
    virtual void GetFeature(GUID featureId, IFeature** ppFeature) = 0; 
}; 

、あなたの機能インタフェースは共通のものから派生しなければならないでしょう。

はいこのソリューションは複数の継承を使用します(具体的なコンポーネントはIFeatureXXインターフェイスとIFullComponentから派生します)が、余分な複雑さがあるため複数の継承を避ける人でも、インターフェイスを継承している実際の実装言語レベルで複数の継承をサポートしていないjavaとC#でも、これを行うことができます。

+0

ありがとう! (1)派生クラスはすべて理論的にすべてのフィーチャを実装できるので、すべてのクラスはすべてのフィーチャを継承する必要があります。(2)フィーチャをコンパイルするクラスです。 (3)単純化のためにvoid returnを使用しましたが、フィーチャメソッドのシグネチャとリターンは大きく異なります。 – dolphy

+0

@dolphy:(1)あなたにはなぜ問題がありますか? 10のインターフェイスを実装する必要があるクラスがある場合、10回継承することに何も問題はありません。しかし、私が提案したインタフェースシグネチャでは、それをする必要はありません。それぞれの機能を別々のクラスに実装することができます。次に、メインコンポーネントからフィーチャーを削除したい場合は、GetFeature()のルックアップテーブルを変更して、1つのメンバー変数を削除するだけです。代わりに、boost :: mpl :: inherit_linearlyを見て、見た目の良いリストに基づいて継承構造を生成できるかどうかを調べることができます。 – DXM

+0

...それはもっと複雑なものですが、それがあなたが探しているものであればわからない。 Btw、私はあなたが神のオブジェクトを作っているように見える他のコメントに同意し、別のクラスに各機能を置くこともそれを助け、あなたのコードをより管理しやすくします。 – DXM

1

fpllowingは、それを使用するクラスの継承を変更する代わりにBaseInterfaceClassを保持する必要があることを前提としています(そうでなければ、各フィーチャにインターフェイスを定義し、必要なものだけ継承する可能性があります)。 あなたが必要とするものだけのインターフェース部分を取得するために、特殊なテンプレートと一緒に多重継承を使用することができます。

template<bool B> class FeatureABase {}; 
template<> class FeatureABase<true> { 
public: 
    virtual void doFeatureA1() = 0; 
    virtual void doFeatureA2() = 0; 
}; 
//similar definitions for FeatureBBase and FutureCBase here 
class BaseInterfaceClass: public FeatureABase<HAS_FEATURE_A>, public FeatureBBase<HAS_FEATURE_B>, public FeatureCBase<HAS_FEATURE_C> 
{/*not flag dependent parts here*/}; 
//If you want to get rid of the preprocessor flags completely, you could define 
//BaseInterfaceClass as a template itself: 
template<bool HasA, bool HasB, bool HasC> 
class BaseInterfaceClass: public FeatureABase<HasA>, public FeatureBBase<HasB>, public FeatureCBase<HasC> 
{/*not flag dependent parts here*/}; 

(あなたがオブジェクトを大きくしたくないので)あなたは多重継承を使用しない場合

template<bool B> class FeatureBBase: public FeatureABase<HAS_FEATURE_A> {}; 
template<> class FeatureBBase<true>: public FeatureABase<HAS_FEATURE_A> { 
public: 
    virtual void doFeatureB1() = 0; 
    virtual void doFeatureB2() = 0; 
    virtual void doFeatureB3() = 0; 
}; 
template<bool B> class FeatureCBase: public FeatureBBase<HAS_FEATURE_B> {}; 
template<> class FeatureCBase<true>: public FeatureBBase<HAS_FEATURE_B> { 
public: 
    virtual void doFeatureC1() = 0; 
} 
class BaseInterfaceClass: public FeatureCBase<HAS_FEATURE_C> 
{/*not flag dependent parts here*/}; 

これはあなたのfeatureflagsは基本的にブール値であると仮定しているので、機能が有効でない場合HAS_FEATURE_A0です、あなたは1つの継承チェーンにそれをアンロールすることができます。そうでない場合は、テンプレートパラメータタイプintを作成し、trueの代わりに1に特化してください。未使用の機能フラグが定義されていない場合は、#ifまたは#ifdefを完全に取り除くことはできません。これは、マクロが定義されているかどうかに基づいて判断する唯一の方法です。

+0

これは興味深いものです。私はこれが長期的な利益になるかどうかを少し決めるためにこれについて考える必要があります...具体的には、機能フラグ(定義されていない0を定義する)を使用する混合方法があります。このような状況では、いくつかの理由(新しい機能の追加、コードを理解している新しい開発者、機能の数が増えている)で継承の連鎖が難しいと私は思う。 – dolphy

+0

@dolphy:そういう混合方法のために、あなたはいつも '#ifndef HAS_FEATURE_B'' #define HAS_FEATURE_B 0'のルートに行くことができます。私は '#if'をどのようにテンプレートの特殊化で置き換えることができるかを見せようとしていました – Grizzly