1

我正在考虑使用工厂函数在同一层次结构中创建不同的类。我了解通常工厂通常执行如下:

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.
    }
}

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

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

4

7 回答 7

1

我会在clonePerson 类中添加一个纯抽象方法(它绝对看起来应该是一个抽象类,主要是为了被子类化而存在——如果你需要一个具体的“以上都不是”类型的 Person,最好通过一个单独的具体子类 OtherKindOfPerson,而不是作为基类本身):

virtual Person* clone() const = 0;

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

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

您还需要将注册表映射更改为:

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

[[您可以使用一些比普通的旧指针更智能的指针Person *,但是由于地图及其所有条目可能需要在该过程中继续存在,这绝对不是什么大问题——您可能从中获得的主要附加值更智能的指针在破坏“指针”时会变得更智能!-)]]

现在,您的工厂函数可以简单地以:

return it->second->clone();

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

子类化一个具体类以产生其他具体类是一个坏主意,因为这些效果可能很棘手并且是错误的来源(参见Haahr反对它的建议:他写的是 Java,但该建议也适用于 C++ 和其他语言[确实,我发现他的建议在 C++ 中更加重要!]。

于 2009-07-04T00:55:28.790 回答
1

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

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

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

于 2009-07-03T22:07:36.213 回答
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类型并使用它来代替上面的“创建者”元素。同样,如您所见,您仅限于工厂“创建”方法中的一组固定参数。

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

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

这比您的方法更灵活,因为您可以创建包含对外部对象(只能在构造期间初始化)或常量成员或在构造后无法以更一般的措辞重置为不同状态的对象的引用的实例。

于 2009-07-03T22:33:41.277 回答
0

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

于 2009-07-03T21:54:44.087 回答
0

您可以对每种类型的人进行枚举:

enum PersonType { student, secretary, professor };
于 2009-07-04T01:19:55.670 回答
-1

好吧,如果您想要一种更快的方法来做到这一点,那么使用枚举和 switch 语句将比处理顺序 if/else if 语句快很多倍......

于 2009-07-03T22:05:54.313 回答
-1

如果您查看您的两个实现,从逻辑上讲它们是相同的。如果展开递归循环,第一个实现与您的第二个实现相同。因此,您的第二次实施确实没有任何优势。

无论您做什么,您都需要列出一些types映射到构造函数的位置。

一种有用的方法是将此地图放在单独的 xml 文件中

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

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

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

于 2009-07-03T22:07:44.573 回答