17

I'm writing a piece of generic software that will be loaded on to many different variants of the same basic hardware. They all have the same processor, but with different peripherals and their own functions that need to be carried out. The software will know which variant it should run by reading a hardware switch value.

Here's my current implementation in a nutshell:

class MyBase
{
public:
    MyBase() { }
    virtual run() = 0;
}


class VariantA : public MyBase
{
public:
    VariantA () { }
    virtual run()
    {
        // Run code specific to hardware Variant-A
    }
}


class VariantB : public MyBase
{
public:
    VariantB () { }
    virtual run()
    {
        // Run code specific to hardware Variant-B
    }
}


void main()
{
    MyBase* variant;
    uint_8 switchValue = readSwitchValue();

    switch(switchValue)
    {
    case 0:
        variant = new VariantA();
        break;

    case 1:
        variant = new VariantB();
        break;
    }

    variant->run();
}

Now this works just fine. I read the hardware value and use a switch statement to create the new corresponding class.

The problem is that there are a lot of variants I have to deal with. Currently about 15, with the potential to add another 20-30 in the near future. I have really come to despise switch statements that run for hundreds of lines, so I'm really looking for a better way to do this, probably through templates.

I want to be able to use my hardware value to look up a type and use that type to create my new object. Ideally when I add a new variant, I create the new class, add that class type to my lookup table with it's matching hardware value, and it's good to go.

Is this possible at all? What's a good solution here?

4

5 に答える 5

21

述べたように、ファクトリを作成しますが、単純な switch ステートメントを使用する必要はありません。できることは、テンプレート クラスを作成して関連するオブジェクトを作成し、これらをファクトリに動的に追加することです。

class VariantinatorBase {
  public:
    VariantinatorBase() {}
    virtual ~VariantinatorBase() {}
    virtual std::unique_ptr<Variant> Create() = 0;
};

template< class T >
class Variantinator : public VariantinatorBase {
  public:
    Variantinator() {}
    virtual ~Variantinator() {}
    virtual std::unique_ptr<Variant> Create() { return std::make_unique<T>(); }
};

これで、これらを登録できるクラス ファクトリができました。

class VariantFactory
{
  public:
    VariantFactory()
    {
         // If you want, you can do all your Register() calls in here, and even
         // make the Register() function private.
    }

    template< uint8_t type, typename T >
    void Register()
    {
        Register( type, std::make_unique<Variantinator<T>>() );
    }

    std::unique_ptr<Variant> Create( uint8_t type )
    {
        TSwitchToVariant::iterator it = m_switchToVariant.find( type );
        if( it == m_switchToVariant.end() ) return nullptr;
        return it->second->Create();
    }

  private:
    void Register( uint8_t type, std::unique_ptr<VariantinatorBase>&& creator )
    {
        m_switchToVariant[type] = std::move(creator);
    }

    typedef std::map<uint8_t, std::unique_ptr<VariantinatorBase> > TSwitchToVariant;
    TSwitchToVariant m_switchToVariant;
};

プログラムの開始時に、ファクトリを作成し、型を登録します。

VariantFactory factory;
factory.Register<0, VariantA>();
factory.Register<1, VariantB>();
factory.Register<2, VariantC>();

その後、それを呼び出す必要があります。

std::unique_ptr<Variant> thing = factory.Create( switchValue );
于 2013-04-16T21:52:54.587 に答える
3

工場をお探しの方

http://www.oodesign.com/factory-pattern.html

ファクトリは、ジョブに適したオブジェクトを作成することだけを目的としたソフトウェア モジュール (メソッド、クラス) です。ファクトリ クラスを使用した例:

class VariantFactory
{
    MyBase* CreateObject(uint_8 value);
}

また、CreateObject メソッドを入力して、必要なオブジェクトのタイプを指定できます。

単純な構造を持つ非常に少数のオブジェクトの選択の場合、単純な switch ステートメントで十分な場合があります。大量のオブジェクトや、より詳細な構築が必要なオブジェクトを取得するとすぐに、ファクトリは非常に役立ちます。

于 2013-04-16T21:38:51.337 に答える
2

更新:後世のために元のソリューションをここに残しますが、パディが提供するソリューションは優れており、エラーが発生しにくいと考えています。いくつかのわずかな改善だけで、実際に得られるものと同じくらい良いと思います.


次の設計を検討してください。

class VariantA : public MyBase
{
    static MyBase *CreateMachineInstance() { return new VariantA; }
};

class VariantB : public MyBase
{
    static MyBase *CreateMachineInstance() { return new VariantB; }
};

ここで必要なのは、std::mapauint_8をキーとして使用し、それを関数ポインターにマップする ( を返すMyBase) だけです。識別子をマップに挿入し (それぞれが適切なマシン作成関数を指すようにします)、コードを読み取り、マップを使用して使用しているマシンを見つけます。

これは、「ファクトリー」と呼ばれる概念/パターンに大まかに基づいていますが、マシンコンストラクターが異なる引数を必要とする場合、またはマシンごとに追加の初期化/操作を実行する必要がある場合、わずかに壊れる可能性があります。

その場合でも、このパターンを使用できますが、いくつかの微調整を行い、物事を少し再構築する必要がありますが、最終的には、よりクリーンで、拡張と保守が容易になるものなります。

于 2013-04-16T21:46:32.527 に答える
2

これをコメントしました。それを答えに変えましょう:

個人的には、適切なクラスを作成するための「スイッチ/ケース」ブロックがおそらく最適なソリューションだと思います。特定のクラスへの参照を返す静的な「ファクトリ」メソッドに case ステートメントを配置するだけです。私見では...

良い例を次に示します:ファクトリ メソッドの設計パターン

Class Book : public Product
{
};

class Computer : public Product
{
};

class ProductFactory
{
public:
  virtual Product* Make(int type)
  {
    switch (type)
    {
      case 0:
        return new Book();
      case 1:
        return new Computer();
        [...]
    }
  }
}

Call it like this:

ProductFactory factory = ....;
Product* p1 = factory.Make(0); // p1 is a Book*
Product* p2 = factory.Make(1); // p2 is a Computer*
// remember to delete p1 and p2

彼の最も優れた応答で、smink は他のデザインの代替案もいくつか提案していることに注意してください。

結論: スイッチ/ケース ブロックには本質的に「問題」はありません。多くのケースオプションがあるスイッチでも。

私見では...

PS: これは実際には「動的型」を作成していません。むしろ、「静的型を動的に作成する」ことです。テンプレートまたは列挙型ソリューションを使用した場合も同様です。しかし、繰り返しになりますが、私は「スイッチ/ケース」を非常に好みます。

于 2013-04-16T21:44:20.780 に答える