设计模式笔记(二十二)– 抽象工厂模式

抽象工厂模式(Abstract Factory Pattern)是另外一种工厂模式,人们经常会将它与工厂方法模式搞混,实际上它们是完全不一样的。抽象工厂模式提供一个接口用于创建相关类似对象。


问题提出

假设我们要创建一个图形框架。在我们的框架里,有Button、TextBox、Drop-down list等部件。我们希望这些部件支持不同的主题样式,比如Material Design,比如Ant等等。

当我们选择某个主题时,我们框架的所有部件都有一致的样式。比如我们选择Material Design,我们将不会看到Ant样式,所有的部件都呈现出Material Design的样式。

下面编写代码模拟。

创建接口Widget,它定义了render方法。

创建接口Button和TextBox,它们均继承于接口Widget。Button和TextBox用接口表示,是因为它们的具体实现有两个主题样式:Material Design和Ant。

为组织代码,我们创建包material,用于容纳material样式的MaterialButton和MaterialTextBox。它们分别实现了接口Button和TextBox。

同样的,创建Ant包,它包含AntButton类和AntTextBox类。

现在,我们的应用程序准备使用这个图形框架。但在此之前,我们先创建枚举类型Theme。

创建ContactForm类,方法render接收一个Theme枚举值,根据该枚举值动态渲染部件的主题样式。

代码有些问题:

首先,它违反了开放闭合原则。明天如果增加一个新的主题样式,我们就得回到ContactForm类修改render方法。

其次,我们必须非常小心地使用相对应的类,不能搞错,比如当枚举值为Theme.ANT时,我们不小心使用了MaterialTextBox类,将会出现我们不希望出现的结果。

使用抽象工厂模式可以解决这些问题。


解决方案

我们来讨论抽象工厂模式(Abstract Factory Pattern)。

首先你得知道工厂方法(Factory Method)是一个方法,正如它的名字一样。而抽象工厂(Abstract Factory)是一个抽象,它是创建相关类似对象的一个接口。

它们是这样工作的。

如上图,WidgetFactory是一个接口,定义了方法createButton和createTextBox。每一个方法都是工厂方法(Factory Method),而WidgetFactory本身是一个抽象,它是一个接口。

我们可以创建WidgetFactory接口的具体实现,比如MaterialWidgetFactory和AntWidgetFactory。MaterialWidgetFactory仅知道如何创建Material主题样式的部件(Widgets),同样AntWidgetFactory也仅知道如何创建Ant主题的部件。

这个就被我们称为抽象工厂模式。

四人帮经典的结构图示如下。


代码实现

创建接口WidgetFactory,它定义了createButton和createTextBox方法。

对于不同的主题样式,我们创建具体不同的实现。首先是实现Material主题的部件工厂。

在material包下,创建MaterialWidgetFactory类,它实现了WidgetFactory接口。

类似地,实现AntWidgetFactory类。

现在到了有趣的部分,我们可以去掉ContactForm类render方法里的丑陋的判断代码,render方法的参数由枚举值Theme改成WidgetFactory接口。render实现也非常简单,调用参数factory的createButton和createTextBox方法创建相应主题样式的Button和TextBox,然后进行渲染。

现在的代码很简洁,非常棒。这种实现遵循了开放闭合原则,明天我们如果添加新主题(Theme),我们不必回头修改ContactForm类,我们只需创建新主题样式,并传递给render即可。

我们到Main中进行测试。

当我们传递给render的factory对象是MaterialWidgetFactor时,则渲染出Material主题样式的部件。

而当我们传递AntWidgetFactor时,则渲染出Ant主题样式。

使用这种模式,我们不必根据选择的主题而记住使用相应的部件,我们不会搞混一个Ant Button和Material TextBox。

再一次说明,抽象工厂提供了创建类似相关对象的一个接口。

但如果您的应用程序里没有这种场景,您不必使用抽象工厂模式。


小结

抽象工厂模式提供了一个接口,用于创建相关类似对象。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注