17

我正在编写一个通用软件,它将加载到相同基本硬件的许多不同变体上。它们都具有相同的处理器,但具有不同的外围设备和它们自己需要执行的功能。软件将通过读取硬件开关值来知道它应该运行哪个变体。

简而言之,这是我当前的实现:

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

现在这工作得很好。我读取了硬件值并使用 switch 语句来创建新的相应类。

问题是我必须处理很多变体。目前大约有 15 个,有可能在不久的将来再增加 20-30 个。我真的开始鄙视运行数百行的 switch 语句,所以我真的在寻找更好的方法来做到这一点,可能是通过模板。

我希望能够使用我的硬件值来查找类型并使用该类型来创建我的新对象。理想情况下,当我添加一个新变体时,我会创建新类,将该类类型添加到我的查找表中,并使用它匹配的硬件值,这样就很好了。

这可能吗?这里有什么好的解决方案?

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

我对此发表了评论;让我们把它变成一个答案:

就个人而言,我认为创建适当类的“switch/case”块可能是最佳解决方案。只需将您的 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 还提出了其他一些设计替代方案。

底线:switch/case 块本身并没有什么“错误”。即使对于具有许多案例选项的开关。

恕我直言...

PS:这真的不是在创建“动态类型”。相反,它是“动态创建静态类型”。如果您也使用模板或枚举解决方案,这同样适用。但同样 - 我非常喜欢“开关/外壳”。

于 2013-04-16T21:44:20.780 回答
2

更新:我将我的原始解决方案留给后代,但考虑到稻田提供的解决方案更优越且不易出错。我认为只有一些细微的改进,它实际上已经达到了你所能得到的最好水平。


考虑这个设计:

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

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

现在,您只需要std::map使用 auint_8作为键并将其映射到函数指针(返回MyBase)。在地图中插入标识符(将每个标识符指向适当的机器创建函数),然后阅读代码并使用地图查找您正在使用的机器。

这大致基于称为“工厂”的概念/模式,但如果您的机器构造函数需要不同的参数或者您需要执行额外的每台机器初始化/操作,则可能会稍微中断 - 从您提到的内容来看,您可能会这样做。

如果是这种情况,您仍然可以使用此模式,但您必须进行一些调整并重新架构一些东西,但您最终会得到一些清洁、更容易扩充和维护的东西。

于 2013-04-16T21:46:32.527 回答
-4
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

template<class T,class T1>
class HeroHonda
{
private:
    T millage;
    T1 *options;

public:
    HeroHonda() {
        puts("constructed");
        options=new T1[20];

        strcpy(options,"Good millage,Powerstart");
        millage=110;
    }

    virtual T features() {
        cout<<options<<"millage is"<<millage<<endl;
        return 1;
    }

    // virtual T Extrafeatures() = 0;

    ~HeroHonda() {
      cout<<"destructor"<<endl;
      delete [] options;    
    }
};

int main()
{
    HeroHonda <int,char> *Ptr=new HeroHonda <int,char>;
    Ptr->features();
    delete Ptr;
}
于 2016-04-22T18:31:45.327 回答