简单工厂和工厂方法设计模式的区别

6
我是一名有用的助手,可以为您翻译文本。以下是需要翻译的内容:

我正在学习设计模式,并尝试理解简单工厂和工厂方法模式之间的区别。首先我想澄清一下,我尝试从Stack Overflow和其他网站阅读了很多相关文章,但这并没有帮助到我。

以下是我的问题:假设我有一个如下所示的产品层次结构:enter image description here

我编写了一个简单的工厂类,如下所示:

public class SimpleItemFactory {

    static Item getItem(String type) {
        if(type.equals("Cake")) {
            return new Cake();
        }else if(type.equals("Chocolate")) {
            return new Chocolate();
        }else {
            return null;
        }
    }
}

现在我已经将所有对象的创建放在一个地方,所以如果明天发生任何变化[比如构造函数需要一个参数],我们只需要在一个地方进行更改。但是它破坏了开闭原则,因为如果明天我们添加更多项目,我们需要更改getItem()方法的条件。所以我们采用工厂方法模式。 enter image description here 我们创建如下所示的工厂类:
public abstract class ItemFactory {
    abstract Item getItem();
}

class CakeFactory extends ItemFactory {
    @Override
    Item getItem() {
        return new Cake();
    }
}

class ChocolateFactory extends ItemFactory {
    @Override
    Item getItem() {
        return new Chocolate();
    }
}


class Client{
    public static void main(String[] args) {

        Item chocolate = new ChocolateFactory().getItem();
        System.out.println(chocolate);
    }
 }

现在当客户想要添加名为IceCream的新项目时,他们只需要创建名为IceCreamFactory的新工厂,并从中创建IceCream,如下所示:

class IceCreamFactory extends ItemFactory{

    @Override
    Item getItem() {
        return new IceCream();
    }

}

class Client{
    public static void main(String[] args) {
        Item iceCream = new IceCreamFactory().getItem();
        System.out.println(iceCream);

    }
}

我的理解正确吗?虽然我们满足了开闭原则,但是对于每个产品(Item),我们都需要一个工厂类,这不会变成难以管理的噩梦吗?
注意:我所指的文章是指https://www.codeproject.com/Articles/1135918/Factory-Patterns-Factory-Method-Pattern?msg=5380215#xx5380215xx

简单工厂违反OCP原则。如果你选择简单工厂,最终会添加数十个if-else语句(在其最简形式下),我认为这不是最佳实践。在这种情况下,我宁愿选择工厂方法。 - okante
3个回答

5
您的理解是正确的,但需要注意每一种设计模式都是为了解决至少一个单一问题,有时会带来其他复杂性或副作用,这意味着没有完美的设计模式可以解决所有问题。
为了学习目的,您可以逐个应用设计模式(这使得设计模式的真正威力似乎害羞或隐藏),但在实际应用中,设计模式会混合在一起(甚至可以发明一种新模式:p)以创建满足大多数需求并接近理想的东西,例如您可以将构建器模式与工厂模式或工厂模式与策略模式混合使用,或者将它们三个混合在一起...
对于您的情况,我建议使用工厂方法模式与简单工厂模式相结合,并结合依赖注入模式创建完美而又符合“开闭”原则的东西。
class ItemFactoryContainer() {

    private Map<String, ItemFactory> factories = new HashMap<>();

    public void register(String name, ItemFactory factory) {
        factories.put(name, factory);
    }

    public ItemFactory getFactory(String name) {
        return factories.get(name);
    }
}

public class ItemFactoryTest {

    public static void main(String...args) {

        ItemFactoryContainer factoryContainer = new ItemFactoryContainer();

        factoryContainer.register("choclate", new ChocolateFactory ());
        factoryContainer.register("cake", new CakeFactory ());
        factoryContainer.register("iceCream", new IceCreamFactory  ());

        Chocolate choclate = factoryContainer.getFactory("choclate").getItem();
        Cake cake = factoryContainer.getFactory("cake").getItem();
    }
}

很好,你明显是指工厂方法有时难以管理。还有一个问题,在你的例子中,如果明天我想添加一个名为“甜甜圈”的项目,那么在ItemFactoryTest中我需要添加/更改factoryContainer来获取“甜甜圈”工厂。 - vicky
1
我的意思是,有些设计模式为了解决问题可能会带来其他副作用,例如增加类的数量,从而增加代码源的大小... - Mouad EL Fakir
1
添加一个甜甜圈项目就像 factoryContainer.register("Donuts", new DonutsFactory ()); 一样简单,这并没有违反开闭原则(实际上这被认为是扩展而不是修改), 在实际应用中,您可能会使用 Spring framework 通过 XML 配置来管理注册部分。 - Mouad EL Fakir
@MouadELFakir 这正是修改,而不是扩展。 - Alessandro

1
我认为在你的例子中,工厂似乎没有用处,因为你的 Cake 和 Chocolate 构造函数不需要任何参数。
当您想要使用许多参数构建复杂对象以尊重 DIP(依赖反转原则)并避免令人尴尬的耦合时,工厂非常有用。
此外,您的示例似乎违反了 LSP(Liskov 替换原则)。Cake 和 Chocolate 类没有任何共同之处,证据是您的抽象超类命名为 Item,这是一个非常通用的名称。
对于我来说,对于复杂的不常见对象,每个类选择一个工厂更好,并有效地遵守 OCP(开闭原则)。
您的第一个示例可用于实例化继承相同超类的类,但使用您的类作为 arg 和反射(使用 java.lang.reflect)调用构造函数,而不仅仅使用字符串评估。 (您的 IDE 无法自动完成此操作),如果没有匹配项,则不返回 null,请抛出 IllegalArgumentException,这样更正确。
像这样:
import java.lang.reflect.Constructor;

public class SimpleItemFactory {
    public <T extends Item> T createItem(Class<? extends Item> itemClass) {
        try {
            // construct your dependencies here
            // and inject them in the getConstructor
            Constructor<? extends Item> constructor = itemClass.getConstructor();
            return (T)constructor.newInstance();
        } catch (Exception e) {
            throw new IllegalArgumentException();
        }
    }
}

并像这样使用它:
class Client {
    public static void main(String[] args) {
        SimpleItemFactory factory = new SimpleItemFactory();
        IceCream iceCream = factory.createItem(IceCream.class);
        System.out.println(iceCream);
        Cake cake = factory.createItem(Cake.class);
        System.out.println(cake);
        Chocolate chocolate = factory.createItem(Chocolate.class);
        System.out.println(chocolate);
    }
}

如果您没有依赖项,可以直接在抽象Item类中将createItem()方法实现为静态方法。

看看这本书,它是一个很好的资源:《Head First设计模式》

OCP只是“开放扩展,关闭修改原则”。如果您需要打开工厂以添加新类(在switch或if中管理的新情况),那么您的工厂不是“OCP”。

不应在构造函数中计算税费,构造函数只应该构造对象。您可以使用策略模式等模式拥有多个taxesCalculator,并将它们注入到构造函数中,使用IOC(控制反转)机制,如依赖注入或简单方式——工厂(可接受),但不要使用类中的简单静态方法。

对于我的糟糕英语,我很抱歉,这对我来说回答这样一个复杂的问题很困难。


嗨@françois-leporcq,抱歉我仍然没有理解你的回答,工厂方法模式是否遵循开闭原则?假设Item类具有复杂的构造函数逻辑,例如在构造函数中计算税费,那么我们应该怎么做?使用工厂方法还是简单工厂? - vicky

1

答案(由@Mouad EL Fakir和@François LEPORCQ提供)涉及静态工厂。那是你的代码的第一部分。

还有两种口味,即工厂方法抽象工厂)

你的代码的第二部分正在使用CakeFactory和ChocolateFactory,你试图使用工厂方法(尽管你的命名表明是抽象工厂)。这里使用getXXX()工厂方法是不正确的。这些模式的目的是不同的。在你的代码中,你没有实现任何东西,只增加了类的数量。(尽管“增加类的数量”和“并行层次结构”被记录为与工厂相关的成本)

我最近回答了一个类似的问题,请看看那些答案是否有帮助

工厂模式中创建者的角色

在下面的例子中,使用抽象工厂而不是工厂方法有哪些真正的好处?


你能解释一下这里的getXXX()方法有什么不正确吗? - vicky
@vicky,我已经更新了回答第一个问题(关于工厂模式中创建者的角色)的内容,以响应类似的请求。请阅读回答的更新部分,其中提到了getXXX()方法的使用。我不希望在这里重复它。 - Kedar Tokekar

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