4

この問題は、私たちの仕事ではよくあることのようです。

ネットワーク経由で int または enum 値を送信し、それを受信して​​、特定のオブジェクト/関数を作成/呼び出したいとします。

最も簡単な解決策は、次のように switch ステートメントを使用することです。

switch (value) {
    case FANCY_TYPE_VALUE: return new FancyType();
}

それは問題なく動作しますが、これらのスイッチ ブロックがたくさんあるため、新しい値と型を作成するときに、それらすべてを変更する必要があります。それは正しいようです。

他の可能性は、テンプレートを使用することです。しかし、enum の値は実行時に定義されるため、できません。

そのための適切な設計パターン、または適切なアプローチはありますか?

これは、毎日のコーディングで非常に一般的で一般的な問題のようです...

4

4 に答える 4

5

マップを試す:

struct Base { };
struct Der1 : Base { static Base * create() { return new Der1; } };
struct Der2 : Base { static Base * create() { return new Der2; } };
struct Der3 : Base { static Base * create() { return new Der3; } };

std::map<int, Base * (*)()> creators;

creators[12] = &Der1::create;
creators[29] = &Der2::create;
creators[85] = &Der3::create;

Base * p = creators[get_id_from_network()]();

(もちろん、これは非常に大雑把です。少なくとも、エラー チェックとクラスごとの自己登録スキームがあれば、クラスの登録を忘れることはありません。)

于 2012-08-06T15:33:50.913 に答える
3

1 つのオプションは、具象型を作成できるクリエーター (同じインターフェースを持つ) の辞書を維持することです。これで、作成コードは辞書で int 値 (クライアントから送信された列挙型の結果) を検索し、create メソッドを呼び出します。このメソッドは、基本クラス ポインターを介して具象オブジェクトを返します。

ディクショナリは、可能な各列挙値に対応する具体的な作成者を使用して、1 か所で初期化できます。

ここでの問題は、新しいタイプのオブジェクトを追加するときに、このディクショナリ初期化コードを拡張する必要があることです。回避方法は以下の通り。

  1. 作成者がシングルトン ファクトリ インスタンスを検索し、concret オブジェクトを作成できる型 enums(integers) を使用してコンストラクターに登録します。
  2. クリエーターの 1 つまたはセットの DLL を作成し、クリエーターのグローバル インスタンスを持ちます。
  3. DLL の名前は、初期化時にファクトリによって読み取られる構成ファイルに入力できます。ファクトリはこのファイル内のすべての DLL をロードし、その結果、ファクトリに登録される静的オブジェクトが作成されます。
  4. これで、ファクトリには、具体的なオブジェクト クリエータで作成できるすべての型列挙型のマップが含まれます。
  5. オブジェクトを作成するために、同じオブジェクト クリエータ ルックアップ メカニズムが実装されます。

ステップ 3、4、および 5 は新しいオブジェクトが導入されても変更されないため、ファクトリを拡張する必要はまったくありません。ステップ 1 は 1 か所で実装できます。

C++ はリフレクションをネイティブにサポートしていないため、新しい具象型ごとにグローバル オブジェクトを追加するだけで済みます。

于 2012-08-06T15:43:36.013 に答える
3

実際には、いくつかのテンプレートトリックを使用してこれを行うことができます:

#include <map>

template <typename Enum, typename Base>
class EnumFactory {
  public:
    static Base* create(Enum e) {
      typename std::map<Enum,EnumFactory<Enum,Base>*>::const_iterator const it = lookup().find(e);
      if (it == lookup().end())
        return 0;
      return it->second->create();
    }
  protected:
    static std::map<Enum,EnumFactory<Enum,Base>*>& lookup() {
      static std::map<Enum,EnumFactory<Enum,Base>*> l;
      return l;
    }
  private:
    virtual Base* create() = 0;
};

template <typename Enum, typename Base, typename Der>
class EnumFactoryImpl : public EnumFactory<Enum,Base> {
  public:
    EnumFactoryImpl(Enum key)
      : position(this->lookup().insert(std::make_pair<Enum,EnumFactory<Enum,Base>*>(key,this)).first) {
    }
    ~EnumFactoryImpl() {
      this->lookup().erase(position);
    }
  private:
    virtual Base* create() {
      return new Der();
    }
    typename std::map<Enum,EnumFactory<Enum,Base>*>::iterator position;
};

enumこれにより、指定された から新しい派生オブジェクトを作成できます。

// will create a new `FancyType` object if `value` evaluates to `FANCY_TYPE_VALUE` at runtime
EnumFactory<MyEnum,MyBase>::create(value)

ただし、一部の関数または名前空間で静的になる可能性があるいくつかの EnumFactoryImpl オブジェクトが必要です。

namespace {
  EnumFactoryImpl<MyEnum,MyBase,Derived1> const fi1(ENUM_VALUE_1);
  EnumFactoryImpl<MyEnum,MyBase,Derived2> const fi2(ENUM_VALUE_2);
  EnumFactoryImpl<MyEnum,MyBase,Derived3> const fi3(ENUM_VALUE_3);
  EnumFactoryImpl<MyEnum,MyBase,FancyType> const fi1(FANCY_TYPE_VALUE); // your example
}

これらの行は、ソース コードがenum値を派生型にマップする単一のポイントです。したがって、すべてが同じ場所にあり、冗長性はありません (これにより、新しい派生型を追加するときに、いくつかの場所で変更を忘れるという問題がなくなります)。

于 2012-08-06T18:32:09.420 に答える
2

kogut、私はこれを答えとして提案しませんが、元の質問に対する私のコメントを拡張するように求められているので、.net 環境が提供するものの非常に簡単な要約を以下に示します...

public enum MyEnum
{
    [MyAttribute(typeof(ClassNone))]
    None,
    [MyAttribute(typeof(ClassOne))]
    One,
    [MyAttribute(typeof(ClassTwo))]
    Two,
    [MyAttribute(typeof(ClassThree))]
    Three
}

つまり、基本的な列挙型の One、Two、Three などがあります。

ただし、MyAttribute というクラスもコーディングします (実際、この領域の詳細については、属性を検索してください)。しかし、ご覧のとおり、これにより、設計時に、これこれの列挙値がこれこれのクラスに関連付けられていると言うことができます。

この情報は列挙型のメタデータ (管理された環境の値!) に保存され、実行時に (Reflection を使用して) 調べることができます。言うまでもなく、これは非常に強力です。このメカニズムを使用して、質問に対する他の回答で提案されている種類のマップを体系的に削除しました。

有用性の例はこれです...私が一緒に働いたあるクライアントでは、テーブルクエリを実行する必要がある人間にとって読みやすいという理由で、ステータスを文字列としてデータベースに保存するという慣例がありました。しかし、これは、ステータスが列挙型としてプッシュされるアプリケーションでは意味がありませんでした。上記の方法 (型ではなく文字列を使用) を使用すると、データの読み取りと書き込みが行われるときに、この変換がコードの 1 行で行われます。さらに、もちろん、MyAttribute を定義したら、任意の列挙型にタグ付けできます。

最近の私の言語はc#ですが、これは(マネージド) c++でも良いでしょう。

于 2012-08-07T09:12:21.950 に答える