什么时候工厂合适?

时间:2010-11-30 09:47:01

标签: design-patterns factory-pattern

取自维基百科:

  

在面向对象的计算机中   编程,工厂是一个对象   用于创建其他对象。它是一个   抽象的构造函数,并且可以   用于实现各种   分配方案。

有人可以解释什么时候需要工厂级或有益吗?

我目前正在开发一个项目,我有一个类并使用构造函数来初始化一个对象(呃!)但它可能会失败并且根本不会初始化。我有一个Success属性来检查它是否正确创建。这是一个应该实施工厂类的好例子吗?这样Create()方法可以返回null,我可以摆脱Success属性。我有正确的想法吗?

4 个答案:

答案 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