前面介绍了简单工厂模式和工厂方法模式,相信你对工厂模式有了一定理解。接下来将继续深入工厂模式,主要介绍工厂方法模式的改进——抽象工厂模式,以及工厂模式在框架源码中的应用,希望在看完这两篇介绍之后能够对彻底搞懂工厂模式,并理解其在实际开发中的意义。
1. 抽象工厂模式
1.1 简介
抽象工厂模式是对抽象方法模式的增强,解决了一个抽象工厂只能生产一个产品的问题。抽象工厂模式在工厂方法模式中的抽象工厂和具体工厂中新增生产产品的方法,从而在进行调用的时候能够拿到多个产品。注意一个工厂生产的多个产品应当是同一类的产品,即产品间有关联(至少在业务逻辑中是有关系的),这样才能够符合设计初衷。
1.2 抽象工厂模式结构
/**
* 抽象工厂类
*/
public class AbstractFactory {
abstract Product create1();
abstract Product create2();
abstract Product create3();
}
/**
* 具体工厂类 1
*/
public class SpecificFactory1 {
public Product create1() {
return new SpecificProduct1();
}
public Product create2() {
return new SpecificProduct2();
}
public Product create3() {
return new SpecificProduct3();
}
}
/**
* 具体工厂类 2
*/
public class SpecificFactory2 {
public Product create1() {
return new SpecificProduct1();
}
public Product create2() {
return new SpecificProduct2();
}
public Product create3() {
return new SpecificProduct3();
}
}
/**
* 抽象产品接口
*/
public interface Product {
void operation();
}
/**
* 具体产品 1
*/
public class SpecificProduct1 implements Product {
public void operation() {
}
}
/**
* 具体产品 2
*/
public class SpecificProduct2 implements Product {
public void operation() {
}
}
/**
* 具体产品 3
*/
public class SpecificProduct3 implements Product {
public void operation() {
}
}
1.3 抽象工厂模式示例
场景模拟:之前去的超市发现餐馆休息之后,大家没地方吃午餐,来超市买零食充饥,超市老板决定在超市里新增一个快餐口,主要卖套餐盒饭,这样周围的上班族能够选择自己喜欢的菜并很快的吃上午餐。这样在超市里,既能够购买零食,还能够买到便当。
// 商铺(抽象工厂类)及对应具体工厂
public abstract class AbstractShop {
abstract Food getFood();
abstract Food getPackedLunch();
}
// 超市(工厂具体实现)
public class Supermarket extends AbstractShop {
Food getFood() {
return new Snacks();
}
Food getPackedLunch() {
return new PackedLunch();
}
}
// 食物(抽象产品)及对应的具体食物
public interface Food {
void eat();
}
// 零食(具体产品)
public class Snacks implements Food {
public void eat() {
System.out.println("eat snacks.");
}
}
// 盒饭便当(具体产品)
public class PackedLunch implements Food {
public void eat() {
System.out.println("eat packed lunch.");
}
}
// 客户调用部分(Client)
public static void main(String[] args) {
AbstractShop shop = new Supermarket(); // 去超市
Food food = shop.getFood(); // 选零食
food.eat(); // 吃零食
Food packedLunch = shop.getPackedLunch(); // 选便当
packedLunch.eat(); // 吃便当
}
控制台打印如下:
eat snacks.
eat packed lunch.
可以看到在同一个超市里面(具体工厂),既能够吃到零食,也能够吃到便当(具体产品),相较于工厂方法模式又有了进一步的增强。
1.4 抽象工厂模式优缺点
优点:
- 弥补了工厂方法模式一个工厂只能生产一个产品的缺点,一个工厂可以管理多个产品的生产
- 在需要多个相关产品的时候,客户端任然只需要一个工厂对象
缺点:
- 同一类产品增加新产品时,需要修改其工厂类
2. 三种工厂模式的比较
- 简单工厂模式是最简单的构建对象的模式,但简单工厂模式并不属于 GoF 23 种设计模式。工厂方法模式和抽象工厂模式则是在简单工厂模式的基础上逐步进行改进增强而得到的
- 简单工厂模式通过静态方法的方式获取产品,工厂方法模式和抽象工厂模式通过创建工厂子类对象的方式获取产品
- 工厂方法模式和抽象工厂模式相比于简单工厂模式在产品生产的选择上进行了改进,将选择权从工厂转移到了客户端,从而达到工厂与产品的解耦,让工厂只专注于生产产品
- 工厂方法模式从抽象工厂中衍生出多个具体的工厂,每个工厂只用于生产一个产品;抽象工厂模式同样是从抽象工厂中衍生出多个具体工厂,但是每个工厂可以生产同一类的多个产品,由抽象工厂决定
3. 工厂模式的实际运用
工厂模式的实际应用非常的广泛,这里就只举几个常见的例子来进行说明
3.1 JDK 中的工厂模式
JDK 中的工厂模式运用比较常见的就是当我们在构建线程池的时候会使用到 Executors 类进行构建,而阿里巴巴的 Java 开发手册中强制不使用 Executors,而是使用 ThreadPoolExecutor 进行手动指定参数进行创建。ThreadPoolExecutor 的构造方法由 7 个参数,这个面试经常会问到,下面列举出来顺便复习一下:
- 核心线程数(corePoolSize):线程池中执行的线程数,即线程池中的线程数,即使没有线程在运行,线程池也会持有这些线程不会销毁
- 最大线程数(maximumPoolSize):最大执行线程数,线程池中可以执行线程数的最大值
- 空闲线程存活时间(keepAliveTime):空闲线程在线程池中的存活时间,当空闲线程的存活时间超过该值时线程池会对其进行销毁,知道只剩 corePoolSize 个线程
- 时间单位(unit):keepAliveTime 空闲线程存活时间的单位
- 阻塞队列(workQueue):当请求线程数大于 maximumPoolSize 时,将多于线程存入该阻塞队列中,BlockingQueue 也有多种实现
- 线程工厂(threadFactory):用来生产线程的线程工厂
- 拒绝策略(handler):当请求线程数超过 maximumPoolSize 并且达到阻塞队列的上限时,对多于线程的处理方式,如直接丢弃、丢弃线程池存在时间最长的线程等
注意到参数中有一个线程工厂,通过 ThreadFactory 这个接口传入,在线程池构建 Worker 的时候会通过调用传入线程工厂对象的 newThread() 方法创建Worker 持有的线程。
// ThreadFactory 接口
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}
// Worker 构造方法
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
这里便是工厂模式的典型应用,在构建 Worker 的时候我们并不关心他是怎么船舰线程的,直接调用对应工厂的 newThread() 方法创建线程即可。在这里的应用中,ThreadFactory 就是抽象工厂,而下面的各种实现类如DefaultThreadFactory、DaemonThreadFactory 等就是对应的具体工厂,而最终构建的线程 Thread 就是工厂所生产的产品。
3.2 Spring 中的工厂模式
Spring 中的工厂模式最有名的就是 BeanFactory 这个接口了,因为我们在获取 bean 时基本上都是从这个接口的实现类中获取的,具体获取过程可以参考:Spring源码分析——获取Bean
从上面的简化图中可以看到在 BeanFactory 这个 bean 工厂体系中,BeanFactory 接口便是抽象工厂,而在实际调用中用的时下面的具体实现子类(具体工厂),如 AbstractBeanFactory 、AbstractApplicationContext、 DefaultListableBeanFactory 等等,而在调用 getBean() 等方法之后获取到的具体实例便是生产出的产品。
3.3 MyBatis 中的工厂模式
在 MyBatis 中有一个接口叫做 SqlSessionFactory ,有 DefaultSqlSessionFactory、SqlSessionManager 这两个。该类的作用在于获取到 SqlSession 对象,并利用该对象对数据库进行增删改查操作,具体源码分析可以参考:MyBatis源码分析——使用SqlSession操作数据库。
在 SqlSessionFactory 这个体系中,各个类在抽象工厂模式中的应用:
- 抽象工厂:接口 SqlSessionFactory
- 具体工厂:DefaultSqlSessionFactory、SqlSessionManager 便是对应的具体工厂,这个两个工厂用于通过调用 openSession() 方法用于获取 SqlSession 对象
- 抽象产品:SqlSession 对象便是具体的抽象产品
- 具体产品:DefaultSqlSession、SqlSessionManager、SqlSessionTemplate 这三个类实现接口 SqlSession ,便是具体的产品
从接口 SqlSessionFactory 中可以看到该工厂不仅能够生产 SqlSession 对象,还能生产 Configuration ,因此整个 SqlSessionFactory 和 SqlSession 体系是个非常标准的抽象工厂模式。