前言
工厂模式(Factory Pattern)是Java中最常用的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的绝佳方式。
工厂方法模式
上篇文章我们学习了简单工厂,但是它不是一种设计模式,反而比较像是一种编程习惯。那么本篇,我们来看一下,“真正的”工厂模式之工厂方法模式。
工厂方法模式属于创建型模式,它定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类,
工厂方法包含如下几个角色:
- 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
工厂方法模式结构
重构一下简单工厂的示例就如下所示:
工厂方法模式重构代码
为了提高代码扩展性,我们需要将简单工厂中的if分支逻辑去掉,通过增加抽象工厂(生产工厂的工厂)的方式,让具体工厂去进行实现,由具体工厂来决定实例化哪一个具体的产品对象。比如宝马汽车下,会有3系、5系、7系等等。
CarFactory
抽象工厂
/**
* @author Duansg
* @date 2022-12-15 12:12 上午
*/
public interface CarFactory {
/**
*
* @param carName
* @param userName
* @return
*/
Response<CarInfo> getCar(String userName);
}
AudiCarFactory
具体工厂
/**
* @author Duansg
* @date 2022-12-17 1:46 上午
*/
public class AudiCarFactory implements CarFactory {
@Override
public Response<CarInfo> getCar(String userName) {
return new AudiCar().sendCars(userName);
}
}
BenzCarFactory
具体工厂
/**
* @author Duansg
* @date 2022-12-15 12:13 上午
*/
public class BenzCarFactory implements CarFactory {
@Override
public Response<CarInfo> getCar(String userName) {
return new BenzCar().sendCars(userName);
}
}
BmwCarFactory
具体工厂
/**
* @author Duansg
* @date 2022-12-15 12:22 上午
*/
public class BmwFactory implements CarFactory {
@Override
public Response<CarInfo> getCar(String userName) {
return new BmwCar().sendCars(userName);
}
}
Test Controller V1
但是通过上面代码,不难看出,我们要利用抽象工厂的能力,但是这些工厂类对象的创建逻辑又耦合进了逻辑方法中,跟我们简单工厂非常相似,只不过多封装了一层,所以引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。
@RequestMapping(value = "/getCarInfo", method = RequestMethod.GET)
public Response<CarInfo> getCarInfo(String carName, String userName) {
CarFactory carFactory = null;
switch (carName) {
case "宝马汽车":
carFactory = new BmwFactory();
break;
case "奔驰汽车":
carFactory = new BenzCarFactory();
break;
case "奥迪汽车":
carFactory = new AudiCarFactory();
break;
}
if (ObjectUtils.isEmpty(carFactory)) {
return Response.failOfMessage("异常");
}
return carFactory.getCar(userName);
}
那怎么来解决这个问题呢?
可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。
/**
* @author Duansg
* @date 2022-12-15 12:36 上午
*/
public class CommonCarFactory {
private static final Map<String, CarFactory> CACHED_FACTORIES = new HashMap<>();
static{
CACHED_FACTORIES.put("宝马汽车", new BmwFactory());
CACHED_FACTORIES.put("奔驰汽车",new BenzCarFactory());
CACHED_FACTORIES.put("奥迪汽车", new BenzCarFactory());
}
public static Response<CarInfo> getCar(String carName, String userName) {
return CACHED_FACTORIES.get(carName).getCar(userName);
}
}
Test Controller V2
那么优化过的代码如下:
@RequestMapping(value = "/getCarInfo", method = RequestMethod.GET)
public Response<CarInfo> getCarInfo(String carName, String userName) {
return CommonCarFactory.getCar(carName, userName);
}
现在我们的代码已经基本上符合了开闭原则,当新增一个新的汽车产品时,我们需要做的事情包括如下:
- 创建新的产品类,并且让该产品实现抽象产品接口。
- 创建产品类对应的具体工厂,并让具体工厂实现抽象工厂。
- 将新的具体工厂对象,添加到CommonCarFactory的CACHED_FACTORIES中即可,对原本类的侵入很少,只需要在静态代码块中新增一行代码,需要改动的代码改动的非常少。
我们什么时候使用工厂方法模式?
- 需要使用很多重复代码创建对象时,比如,Dao层的数据对象、API层的Vo对象等。
- 创建对象要访问外部信息或资源时,比如,读取数据库字段,获取访问授权token信息,配置文件等。
- 创建需要统一管理生命周期的对象时,比如,会话信息、用户网页浏览轨迹对象等。
- 创建池化对象时,比如,连接池对象、线程池对象、日志对象等。这些对象的特性是:有限、可重用,使用工厂方法模式可以有效节约资源。
- 希望隐藏对象的真实类型时,比如,不希望使用者知道对象的真实构造函数参数等。
小结
我们通过一个简单的示例来认识了工厂方法模式,我们也不难总结出如下:
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则。
缺点:
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
- 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。