我希望有一个类,它根据我传递的字符串创建不同类型的对象。根据我的研究,这最好地描述了工厂的设计模式。我正在成功地实现它,但我遇到了一个设计问题:我不知道如何创建具有不同长度构造函数的对象。
让我们举个例子,一个名为Pet的抽象父类。其中有三个孩子:鱼、猫和狗。他们都继承了宠物的重量和颜色,所以他们的建设者也是如此。但是一条鱼可能需要一些鳍和布尔值来判断它是否是咸水鱼。这是一个4参数构造函数。猫想要多少条腿。这是三个参数。这只狗可能有腿的参数,繁殖参数,以及它是否和其他狗一起玩得很好,有5个参数。
在C++中,我理解没有任何反射,所以最常见的做法似乎是将字符串映射声明为函数指针,其中函数指针指向如下所示的函数:
template<typename T> Pet* createObject(int weight, std::string color) {return new T(weight, color);}
同样,我不确定如何在不影响其他对象的构造函数调用的情况下将更多的参数插入到调用中。
我可以想出两个解决办法:创建新函数来接受不同数量的参数,或者为超过一定大小的构造函数设置默认参数。
根据我有多少不同的参数大小,解决方案1似乎有点过分。
工作方法2似乎忽略了构造函数的全部要点,因为在调用构造函数之后,我将被迫分配数据。
还有其他更好的解决办法吗?
发布于 2015-07-17 06:38:58
您可以使用各种模板和完美的转发。
template<typename T, typename... Args>
Pet* createObject(Args&&... args) {
return new T(std::forward<Args>(args)...);
}
但是,由于任何指针都可以转换为它的基类,如果这个函数返回T*
,可能会更好。此外,使用裸指针是不明智的,因为您必须手动删除它们。最好使用shared_ptr
或unique_ptr
。对于这些类,已经有类似的工厂方法:make_shared
和make_unique
(后者仅在C++14中)。或者,如果编译器不支持C++11,则可以使用助推中的shared_ptr
和make_shared
。
当然,当您知道编译时需要创建什么样的类型时,此解决方案就可以工作。如果您必须在运行时决定它,那么整个问题必须从一个不同的方向来考虑,就好像您不知道要创建哪种类型一样,那么您就无法知道给出哪些参数,除非所有类型的参数都是通用的。在这种情况下,您需要的是一个抽象工厂模式。幸运的是,C++ (至少来自C++11)提供了一种在不创建大量类的情况下实现此模式的方法。例如,假设您必须创建从Pet
派生的某个类的实例。宠物的实际种类、大小和其他属性是在其他地方决定的,而宠物的名称是在创建时决定的。那么,你需要一个这样的工厂:
typedef std::function<std::shared_ptr<Pet>(const std::string& name)> PetFactory;
在某种程度上,您决定要创建一个Dog
(我将实际创建参数的意义留给您的想象)。
PetFactory petFactory =
[](const std::string& name) {
return std::make_shared<Dog>(name, 12, "brown", 23.5);
}
当您真正创建它时,您只需要调用工厂:
std::shared_ptr<Pet> pet = petFactory("Pet Name");
发布于 2015-07-17 06:32:11
这就是你所需要的吗(原谅内存泄漏之类的)
#include <map>
#include <string>
// definition of pet hierarcy
class pet_t
{
public:
virtual ~pet_t(void) {}
};
class frog_t : public pet_t
{
public:
frog_t(int) {}
static frog_t *builder(int n) { return new frog_t(n); }
};
class dog_t : public pet_t
{
public:
dog_t(const char *, int) {}
static dog_t *builder(const char *n, int p) { return new dog_t(n, p); }
};
// the per builder function type
typedef pet_t *(*pet_builder_t)(...);
// the map containing per builders: it's indexed by per type name
std::map<std::string, pet_builder_t> registry;
void build_factory(void)
{
registry["frog"] = reinterpret_cast<pet_builder_t>(&frog_t::builder);
registry["dog"] = reinterpret_cast<pet_builder_t>(&dog_t::builder);
}
// the actual factory function
template <class ...Ts>
pet_t *factory(std::string name, Ts&&...ts)
{
pet_builder_t builder = registry[name];
// assume there is something in the map
return builder(std::forward<Ts>(ts)...);
}
int main(int argc, char *argv[])
{
build_factory();
dog_t *dog = dynamic_cast<dog_t *>(factory(std::string("dog"), std::string("pluto"), 3));
frog_t *frog = dynamic_cast<frog_t *>(factory(std::string("frog"), 7));
}
我觉得有太多的演员,但这个想法应该是一个很好的起点。
发布于 2015-07-17 06:48:23
如果您创建一个对象时,您已经知道了它的参数,并且它将是一个鱼,那么您根本不需要一个工厂:只需构造一个Fish,您就完成了。
您可以合理地利用一个工厂,以防您不知道调用方会从中产生哪个对象。例如,将字符串作为工厂方法的输入,可能从文件中读取:工厂通过解析字符串创建并返回正确的对象类型。
调用者不知道它是鱼还是狗:这是工厂方法的目标。
此外,当您可以通过继承添加更多“可构造”对象并重写虚拟创建主义方法来扩展工厂时,您还可以利用它。如果方法有不同的签名--它们实际上是不同的方法,则不会发生这种情况。
https://stackoverflow.com/questions/31477785
复制