万字长文设计模式:工厂方法模式 - “造物主的智慧模具!”
前言
Hi, 我是Rike,欢迎来到我的频道~
- 简单工厂模式中,工厂类的职责过重,导致扩展性不强,新增产品就需要持续更改代码。
为了解决这个问题,诞生了工厂方法模式。
一、介绍
工厂方法模式,是一种创建型设计模式,它让父类在不知道具体实现的情况下,完成自身的功能调用,而具体的实现延迟到子类来实现。
它的核心思想,是将一系列产品的实例化过程,分散在对应具体工厂来实现,即关注的是对象的创建。
详细来说,它存在抽象产品和抽象工厂,每个具体产品都对应了一个具体工厂,通过具体工厂的产品实例化方法来获取所需产品的实例对象。客户在使用时只需要通过工厂类获得实例即可,无需关心具体的对象创建细节。
即,工厂所提供的产品是同一接口下的所有实现类对象。
优缺点如下:
-
优点:
- 封装对象的创建逻辑,隐藏对象的创建细节;
- 解耦客户端与具体对象,只需关注抽象接口,无需关心具体实现;
- 灵活的对象创建方式;
- 遵循开闭原则,扩展性强,只需增加实现类,而无需管理抽象接口。
-
缺点:
- 类个数增加:增加产品,就需增加对应工厂类。
- 增加代码复杂程度,和理解难度。
二、模式原理
工厂方法模式通常包括以下角色:
- 抽象产品(Abstract Product):定义产品的通用属性、方法,提供对外访问途径。
- 具体产品(Concrete Product):实现抽象产品,提供了产品的具体功能和行为(具体实现逻辑),是工厂模式所创建的目标对象。
- 抽象工厂(Abstract Factory):定义产品的生产、业务方法,提供对外访问途径。
- 具体工厂(Concrete Factory):实现抽象工厂,负责创建具体产品的实例。
在工厂方法模式中,客户端通过与具体工厂进行交互,获取具体产品的实例。
本次示例产品抽象与具体之间的关系,如下图所示:
本次示例工厂抽象与具体之间的关系,有两种方式:
- 每个具体产品对应一个创建工厂。
- 所有产品都可放在一个具体工厂中进行创建。
对此,我分别创建了两个接口,第一条对应ConcreteProductFactory
,第二条对应ProductExampleFactory
,都继承Factory
。如下图所示
三、应用实现
(一)应用场景
工厂模式适用于以下场景:
- 提供统一的对象访问方式。
- 需要根据不同条件,创建不同类型的对象时,可用工厂模式。
- 希望通过子类来决定其他具体对象的创建。
- 运行时动态创建具体实现类。
例如,多个保险产品需要通过工厂进行管理和创建。
(二)实现步骤
- 创建产品的抽象与实现;
- 创建抽象工厂;
- 创建具体工厂;
- 应用。
(三)示例代码
关于产品的抽象与实现,仍采用《简单工厂模式》中“拓展延伸-改进版V2”中的产品代码,工厂相关的为新代码。
1. 产品枚举类
@Getter
@AllArgsConstructor
public enum ProductEnum {
PRODUCT_A(0, "产品A"),
PRODUCT_B(1, "产品B");
private final int code;
private final String msg;
/**
* 根据code获取枚举
*
* @param code
* @return
*/
public static ProductEnum parseOfCode(int code) {
return Arrays.stream(values())
.filter(e -> e.getCode() == code)
.findFirst()
.orElse(null);
}
/**
* 根据msg获取枚举
*
* @param msg
* @return
*/
public static ProductEnum parseOfMsg(String msg) {
return Arrays.stream(values())
.filter(e -> e.getMsg().equals(msg))
.findFirst()
.orElse(null);
}
/**
* 获取所有的code
* @return
*/
public static List<Integer> getAllCodes(){
return Arrays.stream(values())
.map(ProductEnum::getCode)
.collect(Collectors.toList());
}
}
2. 产品接口
public interface Product {
/**
* 获取产品枚举
*
* @return 产品枚举
*/
ProductEnum getProductEnum();
/**
* 展示产品
*/
void show();
/**
* 产品功能
*
* @return
*/
List<ModelDto> function(ResolveContext resolveContext, SearchParams searchParams);
}
3. 产品实现抽象类
public abstract class AbsProductHandler implements Product {
/**
* 参数处理,转换为上下文
*
* @param context 上下文
* @param params 参数
*/
protected void resolveParams(ResolveContext context, SearchParams params) {
// 对参数进行处理,封装进context。这里只是简单的赋值,实际业务中可能会有更复杂的处理。
context.setStrField(params.getStrParam());
context.setNumField(params.getNumParam());
context.setListField(params.getParamList());
ModelReqDto modelReqDto = ModelReqDto.builder()
.modelField(context.getStrField())
.build();
context.setInputDto(ResolveInputDto.builder()
.modelReqDto(modelReqDto)
.build());
}
/**
* toModel
*
* @param modelReqDto 请求参数
* @return Model
*/
protected Model toModel(ModelReqDto modelReqDto) {
return Model.builder()
.modelField(modelReqDto.getModelField())
.build();
}
/**
* 模拟查询并且进行业务处理
*
* @param resolveContext 上下文
* @return List<ModelRespDto>
*/
protected List<ModelRespDto> selectList(ResolveContext resolveContext) {
ResolveInputDto inputDto = resolveContext.getInputDto();
ModelReqDto modelReqDto = inputDto.getModelReqDto();
// 模拟查询数据库
List<Model> modelList = Lists.newArrayList(toModel(modelReqDto));
//进行业务处理
// ......
// return
return BeanUtil.copyList(modelList, ModelRespDto.class);
}
}
4. 产品具体实现类
@Service
public class ProductAImpl extends AbsProductHandler {
@Override
public ProductEnum getProductEnum() {
return ProductEnum.PRODUCT_A;
}
@Override
public void show() {
System.out.println("ProductAImpl");
}
@Override
public List<ModelDto> function(ResolveContext resolveContext, SearchParams searchParams) {
// 构建上下文数据
resolveParams(resolveContext, searchParams);
// 具体业务处理
List<ModelRespDto> modelRespDtoList = selectList(resolveContext);
List<ModelDto> modelDtoList = BeanUtil.copyList(modelRespDtoList, ModelDto.class);
System.out.println("ProductAImpl function");
// return
return modelDtoList;
}
}
@Service
public class ProductBImpl extends AbsProductHandler {
private ModelService modelService;
@PostConstruct
public void initModelService(){
modelService = new ModelServiceImpl();
}
@Override
public ProductEnum getProductEnum() {
return ProductEnum.PRODUCT_B;
}
@Override
public void show() {
System.out.println("ProductBImpl");
}
@Override
public List<ModelDto> function(ResolveContext resolveContext, SearchParams searchParams) {
// 构建上下文数据
resolveParams(resolveContext, searchParams);
// 具体业务处理
List<ModelDto> modelDtoList = modelService.list();
System.out.println("ProductBImpl function");
// return
return modelDtoList;
}
}
5. 抽象工厂
public interface Factory {
}
public interface ConcreteProductFactory extends Factory{
/**
* 创建产品实例
*
* @return
*/
Product createProduct();
/**
* 获取工厂类对应产品的enum
*/
ProductEnum getProductCode();
}
public interface ProductExampleFactory extends Factory {
/**
* 创建ProductA实例
* @return ProductA
*/
Product createProductA();
/**
* 创建ProductB实例
* @return ProductB
*/
Product createProductB();
}
6. 具体工厂
@Service
public class ProductAFactoryImpl implements ConcreteProductFactory {
@Resource
private ProductAImpl productA;
@Override
public Product createProduct() {
return productA;
}
@Override
public ProductEnum getProductCode() {
return new ProductAImpl().getProductEnum();
}
}
@Service
public class ProductBFactoryImpl implements ConcreteProductFactory {
@Resource
private ProductBImpl productB;
@Override
public Product createProduct() {
return productB;
}
@Override
public ProductEnum getProductCode() {
return new ProductBImpl().getProductEnum();
}
}
@Service
public class ProductExampleFactoryImpl implements ProductExampleFactory {
@Resource
private ProductAImpl productA;
@Resource
private ProductBImpl productB;
@Override
public Product createProductA() {
return productA;
}
@Override
public Product createProductB() {
return productB;
}
}
工厂类中,我使用Spring注入的方式来获取产品对象,原因如下:
- 产品对象中,也有使用Spring注入的属性。若使用new对象的方式,则注入进去的属性会被覆盖为null。
若产品中无需使用Spring注入的属性,则可以使用new对象的方式创建产品。
7. 应用
@Test
public void factoryMethodTest() {
// 1. 使用工厂类获取产品实例
// 1.1 具体工厂创建具体产品实例
ConcreteProductFactory productAConcreteProductFactory = new ProductAFactoryImpl();
Product productA = productAConcreteProductFactory.createProduct();
ConcreteProductFactory productBConcreteProductFactory = new ProductBFactoryImpl();
Product productB = productBConcreteProductFactory.createProduct();
// 1.2 统一产品工厂及创建多个产品实例
ProductExampleFactory handler = new ProductExampleFactoryImpl();
productA = handler.createProductA();
productB = handler.createProductB();
// 2.通过工厂管理类获取产品示例
FactoryManager factoryManager = new FactoryManager();
productA = factoryManager.getProduct(ProductEnum.PRODUCT_A);
productB = factoryManager.getProduct(ProductEnum.PRODUCT_B);
// 3. 应用
productA.show();
productA.function(new ResolveContext(), new SearchParams());
productB.show();
productB.function(new ResolveContext(), new SearchParams());
}
四、拓展延伸
(一)优化代码
上述基础代码并不利于我们在实际工作中进行应用,可以做以下改动:
- 给抽象工厂和具体工厂中间,增加抽象处理类,设置具体工厂使用的公用方法。
- 设置工厂管理类,统一管理工厂。
抽象工厂处理类:
public abstract class AbsFactoryHandler implements Factory {
/**
* 工厂类公共方法
*/
public void doSomething() {
System.out.println("do something");
}
}
需注意,抽象工厂处理类无需放入IOC容器当中。
在此,我使用Factory来代表我要实现的抽象工厂。在上述示例中抽象工厂有ConcreteProductFactory、ProductExampleFactory两个,它们实现各自的抽象工厂处理类后,再让各自的具体工厂实现类继承抽象处理类即可。
工厂管理类:
需注意,工厂管理类处中放的是相同结构的工厂类型。
@Component
public class FactoryManager {
@Resource
private List<ConcreteProductFactory> concreteProductFactoryList;
private Map<ProductEnum, ConcreteProductFactory> factoryMap;
/**
* 初始化, 将所有的Factory实现类, 按照(k:ProductEnum,v:ConcreteProductFactory)注入到map中
*/
@PostConstruct
private void init() {
factoryMap = concreteProductFactoryList.stream()
.collect(Collectors.toMap(ConcreteProductFactory::getProductCode, Function.identity(), (k1, k2) -> k1));
}
/**
* 根据ProductEnum获取创建对应产品的示例
*
* @param productEnum 产品枚举
* @return Product
*/
public Product getProduct(ProductEnum productEnum) {
return MapUtils.emptyIfNull(factoryMap).get(productEnum).createProduct();
}
/**
* 获取所有的Factory实现类
*
* @return List<ConcreteProductFactory>
*/
public List<ConcreteProductFactory> getFactoryList() {
return concreteProductFactoryList;
}
}
其实,在使用工厂管理类统一进行管理后,我们把将各个要产品看作策略,这样我们就可以将所有的代码示例看作是策略模式和工厂模式的结合,也被称为Provider模式。
(二)简单工厂模式与工厂方法模式
共性:
- 都通过工厂来创建产品对象;
- 都将对象的创建逻辑封装进工厂中;
- 提供灵活性和可维护性,可通过增加和修改工厂类,方便滴扩展和替换产品对象的创建。
区别:
-
简单工厂模式:
- 角色只有工厂类。
- 一个工厂类负责创建多个不同的产品对象。
- 只适用于产品较少,且相对简单的情况。
-
工厂方法模式:
- 角色有抽象工厂和具体工厂。
- 一个产品对应一个具体工厂实现类。
- 扩展性增强,可以方便地增加新的产品类型,同时也增加了类的数量。
参考资料
版权声明:个人学习记录,本博客所有文章均采用 CC-BY-NC-SA 许可协议。转载请注明出处。
若有侵权,请留言联系~
- 程杰 -《大话设计模式》- 第八章 - p.67-72
- 程序员进阶 -《设计模式 -工厂模式》
- java全栈知识体系 -《设计模式 创建型-工厂方法(Factory Method)》
如果您觉得文章对您有帮助,请点击文章正下方的小红心一下。您的鼓励是博主的最大动力!
转载自:https://juejin.cn/post/7355667293285810214