实现工厂的不同方式






4.49/5 (33投票s)
2003年3月4日
6分钟阅读

125223

824
本文介绍了实现工厂的几种不同方法,每种方法都有其优缺点。
引言
有时,您需要在应用程序的某些部分创建类的实例,而您实际上并不需要知道类的具体细节。您只想创建一个实例。
这通常通过使用工厂来解决。构造函数(在大多数情况下,它们是类)的唯一目的是简单地创建其他实例。本文介绍了一些编写此类工厂的不同选择。每种都有其优缺点。
工厂 1:简单工厂
示例 1 包含一个简单的工厂类。
class SimpleClassFactory
{
public:
SimpleClass *createInstance() {return new SimpleClass;}
};
此类仅创建一个另一个类的实例。如果不同类的数量有限,则可以使用此方法。然后,您只需要编写有限数量的类工厂。此方法的缺点是,如果您有许多不同的类,您也需要编写许多工厂类。接下来的替代方法试图解决这个问题。
工厂 2:多工厂
示例 2 包含一个工厂,该工厂能够根据给定参数创建不同种类的实例。假设我们有以下类层次结构
class SimpleBaseClass
{
public:
virtual int getValue() = 0;
virtual void setValue(int value) = 0;
};
class SimpleClass1 : public SimpleBaseClass
{
public:
int getValue() {return m_value;}
void setValue(int value) {m_value = value;}
private:
int m_value;
};
class SimpleClass2 : public SimpleBaseClass
{
public:
int getValue() {return m_value*100;}
void setValue(int value) {m_value = value;}
private:
int m_value;
};
多工厂实现可能看起来像这样
class SimpleClassFactory
{
public:
SimpleBaseClass *createInstance1() {return new SimpleClass1;}
SimpleBaseClass *createInstance2() {return new SimpleClass2;}
};
如果您有一个了解所有需要实例化的不同类的中心点,并且工厂的调用者知道他们想要实例化哪个类,则可以使用此方法。如果工厂的调用者不知道工厂可以生成哪些类,并且只传递一个需要分派到正确实例化的魔术值,则无法使用此多工厂。
工厂 3:分派多工厂
此方法与前一种非常相似,但使用了分派方法。
class SimpleClassFactory
{
public:
SimpleBaseClass *createInstance(int type);
};
SimpleBaseClass *SimpleClassFactory::createInstance (int type)
{
if (type==1) return new SimpleClass1;
else if (type==2) return new SimpleClass2;
else return NULL;
}
如果需要创建某种类型(1 或 2)的实例,则可以像这样创建实例
SimpleBaseClass *simpleInstance = NULL;
SimpleClassFactory simpleClassFactory;
simpleInstance = simpleClassFactory.createInstance(1);
我承认这与方法 2 只有很小的区别,但在某些情况下可能很有用。
工厂 4:函数指针工厂
多工厂的缺点是工厂类需要了解所有不同的类。在以下情况下无法使用它
假设我们有 10 个类。其中 5 个属于在多个应用程序中使用的模块。其中 5 个是应用程序特定的。它们都继承自同一个基类。在应用程序的某个部分,我们需要创建其中一个类的实例。需要实例化的确切类取决于给定的值。
- 我们无法在共享部分编写工厂,因为它只知道 5 个类。
- 我们不想在应用程序特定部分编写工厂,因为这需要我们将它复制到多个应用程序,并且如果向共享模块添加第 6 个共享类(某些应用程序可能会忘记将其添加到应用程序特定的工厂),则可能会出现问题。
我们可以通过使用函数指针来解决这个问题,如下所示
class SimpleClass1 : public SimpleBaseClass
{
public:
int getValue() {return m_value;}
void setValue(int value) {m_value = value;}
static SimpleBaseClass *createInstance() {return new SimpleClass1;}
private:
int m_value;
};
class SimpleClass2 : public SimpleBaseClass
{
public:
int getValue() {return m_value*100;}
void setValue(int value) {m_value = value;}
static SimpleBaseClass *createInstance() {return new SimpleClass2;}
private:
int m_value;
};
每个类都有一个 `static` '`createInstance()`' 方法,用于创建该类的实例。我们需要根据给定值创建实例的方法可能看起来像这样
SimpleBaseClass *someMethod (
std::map<int,FactoryFunction> factoryFunctions, int type)
{
return factoryFunctions[type]();
}
应用程序可以像这样向其提供支持的工厂
std::map<int,FactoryFunction> factoryFunctions;
factoryFunctions[1] = &SimpleClass1::createInstance;
factoryFunctions[2] = &SimpleClass2::createInstance;
然后像这样调用该方法
simpleInstance = someMethod (factoryFunctions,1);
优点是它易于使用,并且不需要每个类一个工厂,也不需要一个了解所有类的大型分派多工厂。此外,该解决方案对于具有纯 C 背景的开发人员来说似乎可以理解。然而,在 C++ 中传递函数指针有时被认为“不当”,在接下来的替代方法中,我将提供一些其他方法。
工厂 5:克隆工厂
克隆工厂方法利用“虚拟”实例进行克隆。我们需要为每个需要由某人实例化的类提供一个克隆方法,如下所示
class SimpleBaseClass
{
public:
virtual int getValue() = 0;
virtual void setValue(int value) = 0;
virtual SimpleBaseClass *clone() = 0;
};
class SimpleClass1 : public SimpleBaseClass
{
public:
int getValue() {return m_value;}
void setValue(int value) {m_value = value;}
SimpleBaseClass *clone() {return new SimpleClass1(*this);}
private:
int m_value;
};
class SimpleClass2 : public SimpleBaseClass
{
public:
int getValue() {return m_value*100;}
void setValue(int value) {m_value = value;}
SimpleBaseClass *clone() {return new SimpleClass2(*this);}
private:
int m_value;
};
需要创建实例的方法现在需要一个这些“虚拟”实例的映射,而不是函数指针
SimpleBaseClass *someMethod (
std::map<int,SimpleBaseClass *> clonables, int type)
{
return clonables[type]->clone();
}
使用方法如下
SimpleBaseClass *simpleInstance = NULL;
SimpleClass1 clonable1;
SimpleClass2 clonable2;
std::map<int,SimpleBaseClass *> clonables;
clonables[1] = &clonable1;
clonables[2] = &clonable2;
simpleInstance = someMethod (clonables,1);
这种方法的优点是我们摆脱了函数指针。但是,我们为此付出的代价是我们需要这些虚拟实例。如果需要实例化的类很简单(并且不使用过多的内存),这可能是一种解决方案。在类实例需要大量内存,或代表更多物理事物(文件、套接字等)的其他情况下,拥有这些虚拟实例可能会很麻烦。
工厂 6:模板工厂
通过这种方法,我们尝试使用模板编写工厂。首先,我们预见到以下两个模板类
template <class BT>
class FactoryPlant
{
public:
FactoryPlant() {}
virtual ~FactoryPlant() {}
virtual BT *createInstance() = 0;
};
template <class BT,class ST>
class Factory : public FactoryPlant<bt>
{
public:
Factory() {}
virtual ~Factory() {}
virtual BT *createInstance() {return new ST;}
};</bt>
`FactoryPlant` 类是实际工厂的超类。在我们之前的示例中,所有可以实例化的类都继承自同一个基类。这是符合逻辑的,因为否则就不可能为这些类编写一个“通用”工厂。传递给 `FactoryPlant` 的类型将由继承自此 `FactoryPlant` 的工厂创建的实例的基类。`Factory` 类接受两种类型:基类和实例化的实际类。
正如您所见,`FactoryPlant` 基类已经包含纯虚 `createInstance` 方法。`Factory` 类实现了该方法,因为它知道实际实例化哪个子类类型。我们现在可以像这样为我们的类提供自己的工厂
class SimpleClass1 : public SimpleBaseClass
{
public:
int getValue() {return m_value;}
void setValue(int value) {m_value = value;}
static Factory<SimpleBaseClass,SimpleClass1> myFactory;
private:
int m_value;
};
Factory<SimpleBaseClass,SimpleClass1> SimpleClass1::myFactory;
class SimpleClass2 : public SimpleBaseClass
{
public:
int getValue() {return m_value*100;}
void setValue(int value) {m_value = value;}
static Factory<SimpleBaseClass,SimpleClass2> myFactory;
private:
int m_value;
};
Factory<SimpleBaseClass,SimpleClass2> SimpleClass2::myFactory;
代替函数指针,类有一个 `static factory` 实例。请注意,由于它是 `static` 的,我们也需要在类外部定义它。否则,我们将遇到未解析的外部符号。为了简化类型的用法,我们还在基类中添加了一个类型定义
class SimpleBaseClass
{
public:
virtual int getValue() = 0;
virtual void setValue(int value) = 0;
typedef FactoryPlant<SimpleBaseClass> SimpleBaseClassFactory;
};
我们需要根据魔术数字创建这些类中每一个的实例的方法现在看起来像这样
SimpleBaseClass *someMethod (
std::map<int,SimpleBaseClass::SimpleBaseClassFactory *> factories,
int type)
{
return factories[type]->createInstance();
}
它被传递了一个工厂映射(所有工厂都继承自 `SimpleBaseClass::SimpleBaseClassFactory`,它实际上是 `FactoryPlant<SimpleBaseClass>`),它调用工厂的 `createInstance`(取决于给定的魔术数字)。然后应用程序可以像这样调用它
SimpleBaseClass *simpleInstance = NULL;
std::map<int,SimpleBaseClass::SimpleBaseClassFactory *> factories;
factories[1] = &SimpleClass1::myFactory;
factories[2] = &SimpleClass2::myFactory;
simpleInstance = someMethod (factories,1);
在此示例中,工厂映射的构建保持相当简单。在更复杂的情况下,映射的一部分可以由共享模块创建。应用程序只需添加自己要支持的工厂到映射中。
结论
有许多不同的方法来编写工厂。我在本文中介绍了其中的 6 种,每种都有其优缺点。在我的情况(我写这篇文章的原因)下,我实际上需要最后一个。我可以使用函数指针方法,但在 C++ 中使用 C 风格的函数指针看起来不太好。我也不太喜欢克隆替代方案,所以我开始尝试模板方法。我希望这篇文章能给您一些启发,如果您还有更多关于编写工厂的想法,请在下方留言。
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。