意图
抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。
简单来说,抽象工厂模式下,我们实际的对象创建都交给了一些工厂类去做,这些工厂类根据具体需求不同,可以创建出很多种类型的工厂类。
问题场景
比如去家具城买家具,如果确定了某种风格的家居装饰(比如:中式),那后续在家具城买的桌子,椅子、床等等家具都是类似风格。
此时需要设法去单独生成每一件家具对象,保证出于一致的风格,最后才能让整个房间看着和谐统一。
但是在代码实现过程中,我们也不希望每次增加新的装修风格时,都需要对已有的代码进行调整,并且家具的产品目录更新迭代非常快,要保证每次调整只是新增,而非修改既有代码。
解决方案
那么针对上面的情况,在抽象工厂模式下,可以为每件家具产品确定一个声明接口,比如椅子,就创建一个Chair接口;然后根据风格不同,每种风格都实现这些接口,这样就组成了一个继承关系:
然后,接下来就需要声明一些抽象工厂,用于创建这一系列的具体家具产品,比如:创建一个FurnitureFactory,它里面定义一系列创建产品的方法,createChair、createSofa等等。
注意,定义的这些方法,返回的都是抽象产品,并不是具体类型的实际产品,然后根据风格,比如ChineseFurnitureFactory、ModerFurnitureFactory。每一种具体的Factory里面才生产具体风格类型的产品。
这么一处理之后,整个模型就差不多出来了:
首先是有了一个抽象工厂类FurnitureFactory,里面定义了一系列抽象方法,主要是各种create方法,创建各种家具产品(桌子、椅子、沙发这类),但是这里的create方法返回类型都是抽象类型
然后定义一系列具体类型的家具工厂类,在这些具体的家具工厂类中,重写各种create方法,来生成具体风格类型的家具产品。
与此同时,定义一些家具产品的接口类(抽象类型),比如Chair、Sofa等等
然后就可以引入一系列各种风格的具体产品类,比如中式椅子(ChineseChair)
这样后续如果有新的风格加入了,比如现在客人需要的是维多利亚风格的家具,那么很简单,FurenitureFactory体系中,加入一种新的VictorianFurnitureFactory,继承自FurenitureFactory,然后创建一系列Victorian风格的家居产品类,继承对应产品类型的抽象类,如:VictorianChair implements Chair等。
代码示意
这里结合一个场景给出一些代码示例,假如我们在开发中,需要开发一些界面,界面上有一系列相关的元素组件,这里只举 Button和Checkbox为例。
此时界面上需要展示一些按钮和复选框,但是不能一刀切全部都是同一种风格,要根据所处平台的不同,展示不同风格的界面:Windows风格、macOS风格等。
此时可以参考上面介绍过的抽象工厂方法模式,我们最终想要的效果可能就是:通过一个GUI对象,调用它的paint方法,根据不同所处的不同平台,最终渲染出不同风格的组件效果。
那么此时可以先设计一个抽象工厂类,它的作用主要就是用来createButton、createCheckbox。此时这两个create方法返回值类型必须是抽象的类型,因此需要有一个Button接口和Checkbox接口作为抽象类型。
然后根据平台需要,这里暂定两种风格:Windows、Mac。
那么就需要有具体风格类型的工厂类,用于创建对应风格的产品:WindowsFactory、MacFactory。这两种factory内部都有对应的create方法,用于创建对应平台风格的产品组件。
然后针对产品组件,也需要进行风格分类,因此就有了WindowsButton、MacButton、WindowsCheckbox、MacCheckbox,它们各自实现Button和Checkbox接口。
WindowsFactory里面创建Windows风格的按钮和复选框对象,MacFactory则创建Mac风格的按钮和复选框对象
源代码
/**
* 这是一个抽象的工厂,它可以通配所有的产品类型
*/
public interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
public class MacOSFactory implements GUIFactory{
@Override
public Button createButton() {
return new MacOSButton();
}
@Override
public Checkbox createCheckbox() {
return new MacOSCheckbox();
}
}
public class WindowsFactory implements GUIFactory{
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
public interface Button {
void paint();
}
public class MacOSButton implements Button {
@Override
public void paint() {
System.out.println("MacOS Button created!");
}
}
public class WindowsButton implements Button{
@Override
public void paint() {
System.out.println("Windows Button created!");
}
}
public interface Checkbox {
void paint();
}
public class MacOSCheckbox implements Checkbox {
@Override
public void paint() {
System.out.println("MacOS checkbox created!");
}
}
public class WindowsCheckbox implements Checkbox{
@Override
public void paint() {
System.out.println("Windows Checkbox created!");
}
}
客户端代码
/**
* 客户端代码
* 此时Factory并不关心具体的Factory类型使用,而是通过抽象接口来进行通配
*/
public class Application {
private Button button;
private Checkbox checkbox;
public Application(GUIFactory factory) {
button = factory.createButton();
checkbox = factory.createCheckbox();
}
public void paint() {
button.paint();
checkbox.paint();
}
}
Demo.java
public class Demo {
/**
* 程序中需要关注factory的类型,并且在运行中创建对应的对象
* 通常使用配置或者环境变量来实现具体类型的创建和初始化
*/
private static Application configureApplication() {
Application app;
GUIFactory factory;
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("mac")) {
factory = new MacOSFactory();
} else {
factory = new WindowsFactory();
}
app = new Application(factory);
return app;
}
public static void main(String[] args) {
Application app = configureApplication();
app.paint();
}
}
实际应用案例
java.sql.Connection
学习过Java的人,学到一定程度,一定都会与数据库打交道,当初在学习Java的过程中,第一次遇到了数据库的问题后,在没有任何其他ORM框架引入的时候,首选的都是java.sql包下面的相关数据库接口来操作数据库。
和数据库打交道,就不得不面临一个类的使用:java.sql.Connection
。
public interface Connection extends Wrapper, AutoCloseable{
//执行静态SQL
Statement createStatement() throws SQLException;
//预占位的SQL
PreparedStatement prepareStatement(String sql) throws SQLException;
//执行数据库存储过程
CallableStatement prepareCall(String sql) throws SQLException;
......
}
而关于Statement
。它实际上是一个接口:
public interface Statement extends Wrapper, AutoCloseable {
......
}
出于安全考虑,Java的课程里面都会给出建议,考虑使用PreparedStatement
。
这里它就涉及到了抽象工厂模式:
首先最大的抽象工厂是Connection
,它里面有一个抽象产品叫Statement
,同时Statement
本身也是一个抽象工厂,它里面也有一些抽象产品PreparedStatement
、CallableStatement
。
那么它这么做有什么好处呢?
好处
我们知道数据库是有很多的,常见的关系型数据库:MySQL、Oracle、SQL Server等等。不同的数据库厂商就会有不同的实现,这样以来,以后不管对接多少数据库种类,各大厂商无需对JDK内部的这个Connection
进行修改,只需要继承它,扩展相应的子类就可以达到数据库连接的多样化。这样JDK相当于只是提供了一个标准框架,后续所有行为都在这个框架体系内,这样可以很容易做扩展和对接。