如何将工厂模式与代码灵活性结合起来

时间:2009-07-03 21:18:50

标签: c++ stl map factory

我正在考虑一个工厂函数来在同一层次结构中创建不同的类。我知道通常工厂通常按如下方式实施:

Person* Person::Create(string type, ...)
{
    // Student, Secretary and Professor are all derived classes of Person
    if ( type == "student" ) return new Student(...);
    if ( type == "secretary" ) return new Secretary(...);
    if ( type == "professor" ) return new Professor(...);
    return NULL;
}

我试图想办法让流程自动化,以便不需要对各种条件进行硬编码。

到目前为止,我能想到的唯一方法是使用地图和原型模式:

地图将在第一个元素中保存类型字符串,在第二个元素中保存类实例(原型):

std::map<string, Person> PersonClassMap;
// This may be do-able from a configuration file, I am not sure
PersonClassMap.insert(make_pair("student", Student(...)));
PersonClassMap.insert(make_pair("secondary", Secretary(...)));
PersonClassMap.insert(make_pair("professor", Professor(...)));

该功能可能如下所示:

Person* Person::Create(string type)
{
    map<string, Person>::iterator it = PersonClassMap.find(type) ;
    if( it != PersonClassMap.end() )
    {
        return new Person(it->second); // Use copy constructor to create a new class instance from the prototype.
    }
}

不幸的是,只有当你只希望工厂创建的类每次都相同时,原型方法才有效,因为它不支持参数。

有人知道是否可以以一种很好的方式做到这一点,还是我坚持使用工厂功能?

7 个答案:

答案 0 :(得分:1)

当客户端提供有关要创建的对象的一些信息时,我通常会构建一个工厂方法(或工厂对象),但是他们不知道结果将是什么具体的类。关于如何表达工厂接口的决定完全取决于客户端具有哪些信息。可能是它们提供了一个字符串(例如,要解析的程序文本),或者一组参数值(如果我们在n空间中创建几何对象,则为维数和大小的数量)。然后,工厂方法检查信息并决定要创建的对象类型或要调用的更具体的工厂。

因此,调用者不应该做出关于建立什么的决定;如果她知道,那么没有工厂的理由。如果要构建的事物列表是开放式的,您甚至可能有一个注册协议,它允许特定的实现提供它们的构造方法和一个允许工厂方法决定调用哪个方法的鉴别器函数。

这在很大程度上取决于哪些信息是必要的,足以决定构建哪种对象。

答案 1 :(得分:1)

您可以注册工厂方法(而不是要复制的预建元素)。这将允许您使用传递给具体工厂的参数调用抽象工厂。这里的限制是所有混凝土工厂的参数集必须相同。

typedef std::string discriminator;
typedef Base* (*creator)( type1, type2, type3 ); // concrete factory, in this case a free function
typedef std::map< discriminator, creator > concrete_map;
class Factory // abstract
{
public:
   void register_factory( discriminator d, creator c ) {
      factories_[ d ] = c;
   }
   Base* create( discriminator d, type1 a1, type2 a2, type3 a3 )
   {
      return (*(factories_[ d ]))( a1, a2, a3 );
   }
private:
   concrete_map factories_;
};

我使用了自由函数创建器来减少示例代码,但您可以定义concrete_factory类型并使用它而不是上面的'creator'元素。同样,正如您所看到的,您只能在工厂'create'方法中使用一组固定的参数。

每个具体工厂都可以将参数传递给给定类型的构造函数:

Base* createDerived1( type1 a1, type2 a2, type3 a3 )
{
   return new Derived1( a1, a2, a3 );
}

这比你的方法更灵活,因为你可以创建实例来保存对外部对象的引用(那些只能在构造期间初始化)或常量成员,或者在构造之后无法重置为不同状态的对象措辞。

答案 2 :(得分:1)

我会向类Person添加一个纯抽象的clone方法(它看起来应该是一个抽象类,主要是为了被子类化而存在 - 如果你需要一个具体的“以上都没有“最好通过一个单独的具体子类OtherKindOfPerson来完成,而不是作为基类本身的人:”

virtual Person* clone() const = 0;

并在每个具体的子类中覆盖它,例如在Student中,new调用特定的具体子类的副本ctor:

Person* clone() const { return new Student(*this); }

您还需要将注册表地图更改为:

std::map<string, Person*> PersonClassMap;

[[你可以使用一些更聪明的指针而不是普通的旧Person *,但由于地图及其所有条目可能需要在这个过程中存活,这绝对不是什么大问题 - 你可以从更聪明的指针中得到的主要附加价值是在破坏“指针”时更聪明的行为! - )]]

现在,您的工厂功能可以简单地结束:

return it->second->clone();

需要进行更改以避免在具有额外属性的子类上使用基类的copy ctor的“切片”效果,以及保留任何虚方法的分辨率。

对具体类进行子类化以产生其他具体类是一个坏主意,因为这些效果可能很棘手并且是bug的来源(请参阅Haahr对它的建议:他写了关于Java的文章,但建议是对C ++和其他语言也有好处[事实上我发现他的建议在C ++中更为重要!]。

答案 3 :(得分:0)

我不熟悉c ++,但在很多语言中都有代表或闭包的概念。意味着不是映射到实例,而是映射到负责创建对象的函数(委托,闭包)。

答案 4 :(得分:0)

你可以为每种类型的人制作一个枚举:

enum PersonType { student, secretary, professor };

答案 5 :(得分:-1)

如果你想要一个更快的方法,那么使用enum和switch语句将比处理顺序if / else if语句快许多倍......

答案 6 :(得分:-1)

如果你看看你的两个实现,逻辑上它们是相同的。 如果递归循环展开,第一个实现与第二个实现相同。所以你的第二次实施没有任何优势。

无论你做什么,你都需要列出你的types映射到你的构造函数的地方。

这样做的一种方法是将这个地图放在一个单独的xml文件中

<person>
   <type> student </type>
   <constructor> Student </type>
</person>
 ....

然后,您可以将此xml文件读入内存并使用反射来获取构造函数。对于给定的类型。鉴于您使用的是C ++,但这并不是那么简单,因为C ++没有标准的反射。您将不得不寻找扩展来为您提供C ++中的反射。

但无论如何,所有这些替代方案都无法摆脱你在原始实现中所做的事情,即:列出从类型到构造函数的映射并搜索地图。