取自维基百科:
在面向对象的计算机中 编程,工厂是一个对象 用于创建其他对象。它是一个 抽象的构造函数,并且可以 用于实现各种 分配方案。
有人可以解释什么时候需要工厂级或有益吗?
我目前正在开发一个项目,我有一个类并使用构造函数来初始化一个对象(呃!)但它可能会失败并且根本不会初始化。我有一个Success属性来检查它是否正确创建。这是一个应该实施工厂类的好例子吗?这样Create()方法可以返回null,我可以摆脱Success属性。我有正确的想法吗?
答案 0 :(得分:7)
Factory情况的教科书示例是当您有一个接口和多个实现时,但您不希望公开这些实现。您实现了一个工厂(方法或类),它根据您传递的参数生成不同实现的实例;但是,由于它通过接口类型返回它们,因此调用者不会受到实现细节的负担。
真实示例:假设您已为流读取器定义了接口,并且实现了从本地文件,网络资源和stdin读取。然后编写一个采用单个参数(URI)的工厂方法,并返回一个合适的读取器。调用者不需要知道实现细节。最好的部分是,当您决定要支持另一种输入方法时,比如数据:URI,您只需添加另一个实现并将其添加到工厂 - 您无需更改调用代码中的任何内容。
答案 1 :(得分:3)
工厂的目的是在运行时确定,因此在编译程序时可能会经常使用不可用的数据,许多class
es中的哪一个都来自具有virtual
的特定类方法,应该为您的程序捕获该数据。
重点是多态语言功能允许您使用不同类型的数据(共享公共基类),调用适合类型的行为。要从中受益,您需要具有不同派生类型的对象。在Comp Sci课程中学习这一点时,您可能需要对每个Derived类型中的一些进行硬编码,并通过指向基类的方法来使用它们。在复杂的现实问题中,不是硬编码创建,而是通常驱动从程序输入到达的数据,例如数据库表,文件和套接字。根据您在每个点上看到的确切内容,您希望创建一个适当类型的对象来表示它,但您可能需要使用编译时已知类型来保留它的记录:指向基类的指针。然后,您不仅可以预先形成基类所承诺的操作 - 其中一些操作可能涉及动态调度到派生类的实现,但您也可以 - 如果需要 - 确切地确定数据的真实类型是什么,并相应地调用操作
例如,假设您阅读了以下文件,该文件显示了您为每种文件收集不同类型的数据的方式:
elephant name Tip-Toes partner Mega
mule name Dare-You mane_length 132
您有以下类heirarchy来表示这些:
struct Animal
{
Animal(const std::string& name) : name_(name) { }
virtual void eat_from(Supplies&) = 0; // animals must do in their own way...
virtual bool can_jump() { return false; } // some animals might, assume not...
std::string name_;
};
struct Elephant : Animal
{
Elephant(const std::string& name, const std::string& partner)
: Animal(name), partner_(partner)
{ }
std::string partner_;
virtual void eat_from(Supplies&) { supplies.consume(Tofu, 10 * kg); }
void swing_trunk(); // something specific to elephants
};
struct Mule : Animal
{
Mule(const std::string& name, double kgs) : Animal(name), kilograms_(kgs) { }
double kilograms_;
virtual void eat_from(Supplies&) { supplies.consume(Grass, 2 * kg); }
virtual bool can_jump() { return true; }
};
工厂方法的工作是区分大象和骡子并返回适当类型的新对象(源自 - 但不仅仅是 - 动物):
Animal* factory(std::istringstream& input)
{
std::string what, name;
if (input >> what && input >> name)
{
if (what == "elephant")
{
std::string partner;
if (input >> partner)
return new Elephant(name, partner);
}
else if (what == "mule")
{
double mane_length;
if (input >> mane_length)
return new Mule(name, mane_length);
}
}
// can only reach here on unparsable input...
throw runtime_error("can't parse input");
}
然后您可以存储Animal *并对它们执行操作:
std::vector<Animal*> animals;
// we expect 30 animals...
for (int i = 0; i < 30; ++i) animals.push_back(factory(std::cin));
// do things to each animal...
for (int i = 0; i < 30; ++i)
{
Animal* p_unknown = animals[i];
std::cout << p_unknown->name() << '\n';
if (Elephant* p = dynamic_cast<Elephant*>(p_unknown))
p->swing_trunk();
}
回到你的问题:
我目前正在开发一个项目,我有一个类并使用构造函数来初始化一个对象(呃!)但它可能会失败并且根本不会初始化。我有一个Success属性来检查它是否正确创建。这是一个应该实施工厂类的好例子吗?这样Create()方法可以返回null,我可以摆脱Success属性。我有正确的想法吗?
不,不是工厂有用的情况,因为仍然只涉及一种类型。只要坚持你所拥有的(在OO意义上),但你可以选择抛出异常,中止程序等,而不是设置一个被调用者可能会或者可能不会检查的标记。
答案 2 :(得分:1)
我对工厂模式的看法总是有所不同,但无论如何我都会给它。
工厂用于创建一组相关的类型。相关并不意味着它们必须完全实现相同的接口。相关意味着如果你有一个类型你需要实例化,然后你需要创建另一个对象,第二个对象的具体(实现)类型取决于第一个对象的具体类型,你需要一个工厂。
只创建一种对象(没有“相关性”)的工厂不是工厂,而是更像战略。
我的上述定义意味着工厂不会像您想象的那样被使用或需要。
对于无法初始化的对象,我建议通过抛出异常而不是依赖于状态字段来实现快速失败的方法。
答案 3 :(得分:1)
我会尝试一个简单的答案:)
来自Wikipedia。
在以下情况下使用工厂模式:
因此,我认为在您的特定情况下,您不需要'工厂'。 但我会说让一个人不受伤害。
工厂的常见用途是,如果要返回接口并隐藏实现类。
例如:
public final class LoginFactory {
private final static Map<SomeEnum, LoginInterface> IMPLEMENTERS = new HashMap<SomeEnum, LoginInterface>();
static {
IMPLEMENTERS.put(SomeEnum.QUICK, new QuickerLoginImpl());
IMPLEMENTERS.put(SomeEnum.SECURE, new SecureLoginImpl());
}
public static LoginInterface getLoginImpl(SomeEnum type) { // my naming is bad ...
return IMPLEMENTERS.get(type);
}
}
通过这种方式,您可以将SecureLoginImpl
更改为MoreSecureLoginImpl
,例如,您的API用户甚至不会注意到这一点。
您可能还想查看此Wiki页面Abstract Factory pattern。