2

在我现在创建的代码中,我有一个对象可以属于两种离散类型,按序列号区分。像这样的东西:

class Chips {
public:
    Chips(int shelf) {m_nShelf = shelf;}
    Chips(string sSerial) {m_sSerial = sSerial;}
    virtual string GetFlavour() = 0;
    virtual int GetShelf() {return m_nShelf;}
protected:
    string m_sSerial;
    int m_nShelf;
}

class Lays : Chips {
    string GetFlavour() 
    {
        if (m_sSerial[0] == '0') return "Cool ranch";
        else return "";
    }
}

class Pringles : Chips {
    string GetFlavour() 
    {
        if (m_sSerial.find("cool") != -1) return "Cool ranch";
        else return "";
    }
}

现在,实现这一点的明显选择是使用工厂设计模式。手动检查哪个序列属于哪个类类型不会太困难。

但是,这需要一个知道所有其他类并通过 name引用它们的类,这几乎不是真正的通用,特别是如果我最终不得不添加一大堆子类。

更复杂的是,在我知道它的实际序列号之前,我可能必须在一个对象周围保留一段时间,这意味着我可能必须编写充满虚拟函数的基类,而不是保持抽象并以某种方式将其替换为实例当我收到连续剧时,其中一个孩子的班级。这也不太理想。

工厂设计模式真的是解决这个问题的最佳方法,还是有人有更好的主意?

4

3 回答 3

2

您可以创建一个只知道 Base 类的工厂,如下所示:

将纯虚方法添加到基类: virtual Chips* clone() const=0;并为所有派生实现它,就像operator=但返回指向新派生的指针一样。(如果你有析构函数,它也应该是虚拟的)

现在你可以定义一个工厂类:

Class ChipsFactory{
  std::map<std::string,Chips*> m_chipsTypes;

public:
  ~ChipsFactory(){
     //delete all pointers... I'm assuming all are dynamically allocated.
     for( std::map<std::string,Chips*>::iterator it = m_chipsTypes.begin();
          it!=m_chipsTypes.end(); it++) {
        delete it->second;
     }
  }
  //use this method to init every type you have
  void AddChipsType(const std::string& serial, Chips* c){
    m_chipsTypes[serial] = c;
  }      
  //use this to generate object
  Chips* CreateObject(const std::string& serial){
    std::map<std::string,Chips*>::iterator it = m_chipsTypes.find(serial);
    if(it == m_chipsTypes.end()){
       return NULL;
    }else{
       return it->clone();
    }   
  }
};

使用所有类型初始化工厂,您可以从中获取初始化对象类型的指针。

于 2013-11-12T10:04:57.957 回答
1

为什么要在这里麻烦继承?据我所见,所有 Chips 实例的行为都是相同的。这种行为是风味由序列号定义。

如果序列号只改变了几件事,那么您可以使用简单的映射在运行时基于序列号注入或查找行为(std::function)(为什么要复杂化!)。这种方式通过它们的序列号映射在不同的芯片之间共享常见的行为。

如果序列号改变了很多东西,那么我认为你的设计有点倒退。在这种情况下,您真正​​拥有的是定义芯片配置的序列号,您的设计应该反映这一点。像这样:

class SerialNumber {
public:
    // Maybe use a builder along with default values
    SerialNumber( .... ); 
    // All getters, no setters.
    string getFlavour() const;
private:
    string flavour;
    // others (package colour, price, promotion, target country etc...)
}

class Chips {
public:
    // Do not own the serial number... 'tis shared.
    Chips(std::shared_ptr<SerialNumber> poSerial):m_poSerial{poSerial}{} 
    Chips(int shelf, SerialNumber oSerial):m_poSerial{oSerial}, m_nShelf{shelf}{}
    string GetFlavour() {return m_poSerial->getFlavour()};
    int GetShelf() {return m_nShelf;}
protected:
    std::shared_ptr<SerialNumber> m_poSerial;
    int m_nShelf;
}

// stores std::shared_ptr but you could also use one of the shared containers from boost.
Chips pringles{ chipMap.at("standard pringles - sour cream") };

这样,一旦您为您的产品设置了一组序列号,那么产品行为就不会改变。唯一的变化是封装在序列号中的“配置”。意味着Chips类不需要改变。无论如何,有人需要知道如何构建类。当然,您也可以使用基于模板的注入,但您的代码需要注入正确的类型。

最后一个想法。如果SerialNumberctor 接受一个字符串(例如 XML 或 JSON),那么您可以让您的程序在运行时读取配置,在它们由经理类型人员定义之后。这将使业务需求与您的代码分离,这将是一种面向未来的稳健方式。

哦...我建议不要使用匈牙利符号。如果您更改对象或参数的类型,您还必须更改名称。更糟糕的是,您可能会忘记更改它们,而其他人会做出不正确的假设。除非您使用 vim/notepad 进行编程,否则 IDE 将以更清晰的方式为您提供该信息。

@ user1158692 - 实例化的一方Chips只需要了解SerialNumber我提议的设计之一,并且该提议的设计规定SerialNumber该类用于配置Chips该类。在这种情况下,Chips由于他们的亲密关系,使用的人应该知道 SerialNumber。类之间的亲密关系正是它应该通过构造函数注入的原因。当然,如果需要,可以非常简单地更改它以使用 setter,但由于表示的关系,这是我不鼓励的。

我真的怀疑在不知道序列号的情况下创建芯片实例是绝对必要的。我想这是一个应用程序问题,而不是课程设计所要求的问题。此外,如果没有 SerialNumber,该类不是很有用,如果您确实允许在没有 SerialNumber 的情况下构建该类,您将需要使用默认版本(需要 Chips 知道如何构造其中一个或使用全局引用!)或者您最终会通过大量检查来污染班级。

至于您对 shared_ptr 的投诉……您到底是如何建议澄清所有权语义和责任的?也许原始指针将是您的解决方案,但这是危险且不清楚的。shared_ptr 清楚地让设计人员知道他们不拥有该指针并且不对它负责。

于 2013-11-12T11:01:28.807 回答
1

从评论中,我认为您追求的是这样的:

    class ISerialNumber
    {
    public:
        static ISerialNumber* Create( const string& number )
        {
            // instantiate and return a concrete class that 
            // derives from ISerialNumber, or NULL
        }

        virtual void DoSerialNumberTypeStuff() = 0;
    };

    class SerialNumberedObject
    {
    public:
        bool Initialise( const string& serialNum ) 
        {
            m_pNumber = ISerialNumber::Create( serialNum );
            return m_pNumber != NULL;
        }

        void DoThings()
        {
            m_pNumber->DoSerialNumberTypeStuff();
        }

    private:
        ISerialNumber* m_pNumber;
    };

(因为这是一个关于更高级概念的问题,所以防止空/无效指针问题留给读者练习。)

于 2013-11-12T10:30:59.023 回答