通过工厂方法向特定子类传递参数

9
假设我有一个抽象类Drink,以及一个工厂方法,在运行时选择要创建的饮料类型(葡萄酒、啤酒等)。
每种饮料需要一些参数才能正确初始化自己。其中一些对所有饮料都是通用的;例如,它们可能都需要一个DrinkConfig参数。
但是每种饮料可能还有自己独特的要求。也许葡萄酒需要Sommelier助手对象来初始化自己。啤酒不需要这个,但它可能需要自己的助手对象。
那么我应该传递什么给工厂方法呢?当我调用它时,我拥有所有助手对象,因此我可以将它们全部传递给工厂。但这可能会有很多参数。有没有更好的设计方式?
编辑:假设我不能在工厂中创建助手对象;它们只能从调用者那里获得。
7个回答

4

我建议在您的工厂类中创建不同的重载方法。

public class DrinkFactory {

    public static Drink CreateBeer(DrinkConfig config, string hops) {
        return new Beer(config, hops);
    }

    public static Drink CreateWine(DrinkConfig config, string grapes, int temperature) {
        return new Wine(config, grapes, temperature);
    }
}

编辑:

如果希望在工厂类中只有一个方法,另一种实现方式是:

public enum DrinksEnum {
    Beer,
    Wine
}

public class DrinkFactory {

    public static Drink CreateDrink(DrinksEnum drinkType, DrinkConfig config) {
        switch(drinkType) {
            case DrinksEnum.Beer:
                return new Beer(config);
            case DrinksEnum.Wine:
                return new Wine(config);
            default:
                throw new ApplicationException("Drink type not recognised.");
        }
    }
}

签名没问题。问题在于如何将参数传递给您的Factory::CreateDrink()(或者它被称为什么)。 - dirkgently
如果你想在工厂中有一个CreateDrink方法,你可以使用枚举参数来指定你想要的饮料类型。我不认为这种方法或上述方法符合GoF工厂模式,在该模式中,对象是在Drink类中创建的(我今晚需要查一下我的书),但我发现这更加实用,仍然保持了将对象创建集中化到子类层次结构的主要优点。 - sipsorcery
在查看了我的《GoF设计模式》书籍之后,我很高兴地发现我上面提供的示例与如何使用工厂作为抽象工厂设计模式的一部分非常接近。为了完全符合规范,应该有一个抽象工厂类,DrinkFactory从中继承,但对于像这样简单的情况,我通常会省略它。如果需要另一个具体工厂,重构DrinkFactory将变得非常容易。 - sipsorcery
1
很遗憾,这对我的情况并不适用。具体类型是在运行时选择的,因此我无法选择调用特定的CreateBeer或CreateWine方法。我只是调用一个通用的CreateDrink方法,它返回啤酒或葡萄酒。 - JW.
很遗憾,您的编辑示例未回答他的问题:如何处理不同的签名? - koenmetsu
我喜欢这个解决方案,但如果将其推广到各种类型的类和它们的工厂,你会看到enum ClassesEnum和相应的ClassSpecificConfig的爆炸,这是不可避免的吗?也许在php/python中可以将ClassConfig声明为array/dict,但是强类型/静态语言怎么办?(以及ClassesEnum,如果我们也能摆脱它) - Sudhi

2

工厂方法应该抽象化创建值的细节。因此,你不应该将帮助对象传递给工厂方法,而是应该由工厂方法创建其所需的帮助对象,并将其传递给适当的构造函数。


1

工厂应该首先创建非常相似的对象。这意味着即使所有这些对象都是饮料,工厂方法可能并不适用,因为每种饮料与另一种饮料完全不同。

话虽如此,您可以传递一个对象列表,其大小等于要设置的属性数量。然后,每个对象将表示您要在适当对象的构造函数中设置的值,以您想要设置这些变量的顺序排列。这样做的缺点是,在调用之前,您必须在工厂外部格式化列表,这有点笨拙。


0

我倾向于提供一个天真的解决方案,其中您的配料来自基类"DrinkIngredients"。您必须匹配要用于特定饮料的子类。

显然,您可能会尝试创建另一个工厂来生产配料--但这会导致先有鸡还是先有蛋的问题。


0
通常,工厂方法存在于隐藏这些细节的情况下。一个重要的问题是Sommelier来自哪里——如果所有这些其他帮助程序都是单例或可以从已知来源获取,则实例化工厂需要必要的信息去找到它们,这样你的调用代码就不需要担心它。
此外,在许多情况下,像Spring这样的框架将被用于允许您在配置文件中描述这些关系,而不是在代码中。
如果您真的需要从调用代码传递帮助程序,请阅读论文“Arguments and Results”(http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.50.7565),该论文描述了一种用于编组复杂参数的常见模式。基本上,您将创建一个必要参数的中间集合,并将其传递给工厂。

0
在这种情况下,我通常会寻找其他解决方案,而不是传递变量。
例如,在您的情况下 - WineFactory 需要一位侍酒师,以便它可以构建适当的葡萄酒 -
这是运行时依赖注入的一个很好的用例。某种形式的依赖注入框架将使此过程非常简单、易于理解,并且只需要进行所有这些属性的传递即可正常工作。

0
这似乎是使用“建造者”模式的完美案例。对于创建类似对象,请使用“工厂”模式,而对于构建复杂、不同对象,请使用“建造者”模式。尝试使用“工厂”模式解决此问题将导致许多不同的初始化构造函数(具有不同数量/类型的参数)用于不同的对象。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接