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

发布于 2024-07-26 06:50:24 字数 1256 浏览 2 评论 0原文

我正在考虑使用工厂函数在同一层次结构中创建不同的类。 据我了解,通常工厂通常是按如下方式实现的:

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

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

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

I am considering a factory function to create different classes in the same hierarchy. I understand that normally a factory is normally implemented as follows:

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

I am trying to think of a way so that the process can be automated so that the various conditions do not need to be hard-coded.

So far the only way I can think of is using a map and the prototype pattern:

The map will hold the type string in the first element and a class instance (prototype) in the second:

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

The function may look something like this:

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

Unfortunately, the prototype method only works if you only want the class created by the factory to be identical every time, since it does not support arguments.

Does anybody know if it is possible to do it in a nice way, or am I stuck with the factory function?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

赠佳期 2024-08-02 06:50:24

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

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

这在很大程度上取决于哪些信息对于决定构建哪种类型的对象是必要和充分的。

I usually build a factory method (or a factory object) when the clients will be providing some information about the object to be created, but they don't know what concrete class the result will be. The determination about how to express the interface to the factory depends completely on what information the clients have. It could be that they provide a string (program text to be parsed, for example), or a set of parameter values (number of dimensions and sizes if we're creating geometric objects in n-space). The factory method then examines the information and decides what kind of object to create or which more specific factory to call.

So the decision about what to build shouldn't be made by the caller; if she knows, then there's no reason for a factory. If the list of things to be built is open-ended, you might even have a registration protocol that allows specific implementations to provide both their construction method and a discriminator function that would allow the factory method to decide which method to call.

It very much depends on what information is necessary and sufficient to decide which kind of object to build.

佞臣 2024-08-02 06:50:24

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

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

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

You can register a factory method (instead of the prebuilt element to copy). This will allow you to call the abstract factory with parameters that are passed to the concrete factory. The limitation here is that the set of parameters of all concrete factories must be the same.

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_;
};

I have used free function creators to reduce the sample code, but you can define a concrete_factory type and use it instead of the 'creator' element above. Again, as you can see, you are limited to a fixed set of arguments in the factory 'create' method.

Each concrete factory can pass the arguments to the constructor of the given type:

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

This is more flexible than your approach as you can create instances that hold references to external objects (those can only be initialized during construction) or constant members, or objects that cannot be reset to a different state after construction in a more general wording.

神回复 2024-08-02 06:50:24

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

virtual Person* clone() const = 0;

并在每个具体子类中重写它,例如在 Student 中,使用调用特定具体子类的 new子类的复制构造函数:

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

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

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

[[您可以使用一些比普通旧 Person * 更智能的指针,但由于映射及其所有条目可能需要保存为只要这个过程确实如此,这绝对不是什么大问题——您可以从更智能的指针中获得的主要附加值是在破坏“指针”时更智能的行为!-)]]

现在,您的工厂函数可以简单地以以下方式结束:

return it->second->clone();

需要进行这些更改以避免在具有额外属性的子类上使用基类的复制构造函数的“切片”效果,并保留任何虚拟方法的解析。

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

I would add a pure abstract clone method to class Person (which definitely looks like it should be an abstract class, existing mainly for the sake of being subclassed -- if you need a concrete "none of the above" kind of Person it's best done via a separate concrete subclass OtherKindOfPerson, rather than as the base class itself):

virtual Person* clone() const = 0;

and override it in every concrete subclass, e.g. in Student, with a new that invokes the specific concrete subclass's copy ctor:

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

You also need to change the registry map to:

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

[[You could use some smarter pointer than a plain old Person *, but as the map and all of its entries probably needs to survive as long as the process does, this is definitely not a big deal -- the main added value you might get from smarter pointers being smarter behavior upon destruction of the "pointer"!-)]]

Now, your factory function can simply end with:

return it->second->clone();

The changes are needed to avoid the "slicing" effect of using the base class's copy ctor on a subclass that has extra attributes, as well as preserve any virtual method's resolution.

Subclassing a concrete class to yield other concrete classes is a bad idea exactly because these effects can be tricky and a source of bugs (see Haahr's recommendation against it: he writes about Java, but the advice is also good for C++ and other languages [indeed I find his recommendation even more crucial in C++!].

后来的我们 2024-08-02 06:50:24

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

I am not familar with c++ but in many langugaes there concepts of delegates or closures. Means that instead of mapping to instance, you map to the function(delegate, closure) that is responsible to create object.

偏爱自由 2024-08-02 06:50:24

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

enum PersonType { student, secretary, professor };

You could make an enum of each type of person:

enum PersonType { student, secretary, professor };
旧故 2024-08-02 06:50:24

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

Well if you want a faster way to do it then using an enum and a switch statement will be many time faster than processing sequential if/else if statements ...

梦太阳 2024-08-02 06:50:24

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

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

一种有用的方法是将此映射放在单独的 xml 文件中,

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

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

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

If you look at your two implementations, logically they are identical.
The first implementation is the same as your second if the recursive loop was unrolled. So really there is no advantage of your second implementation.

Regardless what you do you will need to list some where your types mapped to your constructors.

One way of doing it that can be useful is to have this map in a seperate xml file

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

You can then read this xml file in to memory and use reflection to get back your constructor. For the given type. Given that you are using C++ however this will not be that simple as C++ does not have reflection as standard. You will have to look for an extension to provide you with reflection in C++.

But regardless, all these alternatives can not escape what you did in your original implementioan, ie: list the mapping from type to constructor and search the map.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文