前言
Hi, 我是Rike,欢迎来到我的频道~
本篇为大家带来的是,设计模式-简单工厂模式,它并不属于GoF23个经典设计模式,但通常将它作为学习其他工厂模式的基础,设计思想十分简单精妙。 同时,在文章最后,我们再与Spring框架相结合,方便我们更专注于业务开发。
希望各位多多支持~
一、介绍
简单工厂模式,是一种创建型设计模式,用一个专门的工厂类来统一管理所有产品对象的创建过程。
详细来说,它将某一系列产品的实例化过程,封装到一个工厂类的方法中,根据需求来获取具体对象,而无需直接使用 new 关键字来实例化对象。
一个接口及其所有实现类都属于一类产品,他们属于同一个产品结构。
优缺点如下:
-
优点:
- 简化对象的创建。
- 隐藏了对象创建的内部细节。
- 可以根据配置灵活创建对象。
-
缺点:
- 违反开闭原则:每当新增一个工厂,就需要修改工厂类的代码。
- 违反单一职责原则:工厂类负责多个对象的创建,可能包含了对象创建的多个逻辑,职责过重。
二、模式原理
在简单工厂模式中,有三个主要角色:
- 工厂(Factory):核心角色,作为调用者和具体产品实现的中间角色,包含一个或多个创建产品的静态方法。
- 抽象产品(Abstract Product):定义通用属性和方法,为对外提供访问途径。
- 具体产品(Concrete Product):实现抽象产品,提供了产品的具体功能和行为(具体实现逻辑)。
工厂类的作用,就是获取产品实例(唯一实例/多例)。
可以理解为根据具体实现类的唯一标识,从工厂类中获取对应实例。
本次示例的类图如下所示:
标识可随需求而定,我采用枚举作为具体产品实现类的唯一标识。
三、应用实现
(一)应用场景
简单工厂模式,适用于以下场景:
- 完全封装隔离某个具体实现,只能通过对外接口操作封装体。
- 对外创建对象的职责进行集中管理和控制。
(二)实现步骤
- 创建抽象产品接口;
- 定义产品实现枚举类;
- 创建具体产品实现类;
- 创建工厂类;
- 应用。
(三)示例代码
3.1 创建抽象产品接口
Java
public interface Product {
/**
* 获取产品枚举
*
* @return 产品枚举
*/
ProductEnum getProductEnum();
/**
* 产品功能
* @return
*/
void function() {
}
3.2 定义产品实现枚举类
Java
@Getter
@AllArgsConstructor
public enum ProductEnum {
PRODUCT_A(0, "产品A"),
PRODUCT_B(1, "产品B");
private int code;
private String name;
}
3.3 创建具体产品实现类
Java
@Service
public class ProductAImpl implements Product {
@Override
public int getCode() {
return ProductEnum.PRODUCT_A.getCode();
}
@Override
public void function() {
System.out.println("ProductBImpl");
}
}
@Service
public class ProductBImpl implements Product {
@Override
public int getCode() {
return ProductEnum.PRODUCT_B.getCode();
}
@Override
public void function() {
System.out.println("ProductBImpl");
}
}
3.4 创建工厂类
Java
public class ProductFactory {
public static Product getSimpleProduct(ProductEnum paramEnum) {
ProductEnum productEnum = ProductEnum.parseOfCode(paramEnum.getCode());
switch (productEnum) {
case PRODUCT_A:
return new ProductAImpl();
case PRODUCT_B:
return new ProductBImpl();
default:
return null;
}
}
}
3.5 应用
通过具体产品实体类的枚举值,去到工厂类中获取对应实例。
Java
@Test
public void simpleFactoryTest() {
Product product1 = SimpleFactory.getProduct(ProductEnum.PRODUCT_A);
if (Objects.nonNull(product1)) {
System.out.println(product1.getCode());
}
Product product2 = SimpleFactory.getProduct(ProductEnum.PRODUCT_B);
if (Objects.nonNull(product2)) {
System.out.println(product2.getCode());
}
}
四、拓展延伸
上述写法虽然能够便利获取具体产品实现,但是只要新增产品实现,就需要更改工厂类,违反了开闭原则。
在工厂类中,可结合使用Spring框架的@PostConstruct,将具体产品实现类整合进List,然后放入IOC容器中。工厂类只需拿唯一标识,在该List中进行比对判断,符合条件即可返回实例。
实现类、工厂类必须带@Component。
(一)改进工厂类版
具体改进如下:
Java
@Component
public class ProductFactory {
@Resource
private List<Product> productList;
private Map<ProductEnum, Product> productMap;
/**
* 初始化, 将所有的Product实现类, 按照(k:ProductEnum,v:ProductImpl)注入到map中
*/
@PostConstruct
private void init() {
productMap = productList.stream()
.collect(Collectors.toMap(Product::getProductEnum, Function.identity(), (k1, k2) -> k1));
}
/**
* 根据ProductEnum获取对应的Product
*
* @param productEnum 产品枚举
* @return Product
*/
public Product getProduct(ProductEnum productEnum) {
return MapUtils.emptyIfNull(productMap).get(productEnum);
}
/**
* 获取所有的Product实现类
*
* @return List<Product>
*/
public List<Product> getProductList() {
return productList;
}
}
当程序启动时,Spring会扫描所有Product的实现类放进productList。每次提取时,只需将List中的按照(key: code, value:impl)放入map,再从map中比对,比对成功则返回实例,否则返回null。
(二)改进版V1
上述代码在我们工作时,实在简陋,无法支撑复杂系统代码实现的需要,因此我们可以对其优化升级。可做如下改动:
- 增加产品中间处理类。
在产品的抽象接口与具体实现类中间增加一个抽象处理类,实现接口、被具体实现类继承,在其中可设置一些公用方法,供具体产品使用。具体实现类只需实现自身独立业务即可。尽可能符合开闭原则设计。
若抽象处理类中的方法,需要被具体实现类调用,就需要将此方法暴露出来,成功一个公共接口方法。与其他接口方法唯一不同的是,该方法是在抽象处理类中被实现,其他接口方法是在具体实现类实现。
需注意,抽象工厂处理类无需放入IOC容器当中。
示例如下:
产品枚举类:
Java
@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());
}
}
产品接口:
Java
public interface Product {
/**
* 获取产品枚举
*
* @return 产品枚举
*/
ProductEnum getProductEnum();
/**
* 展示产品
*/
void show();
/**
* 产品功能
*
* @return
*/
List<ModelDto> function(ResolveContext resolveContext, SearchParams searchParams);
}
在其中增加和改进了方法,使其更符合工作。
产品实现抽象类:
Java
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);
}
}
产品具体实现类:
Java
@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 {
@Resource
private ModelService modelService;
@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;
}
}
工厂类:
Java
@Component
public class AbsFTYsToolHandlerFactory {
@Resource
private List<FactoryToolHandler> handlerList;
private Map<FactoryToolEnum, FactoryToolHandler> handlerMap;
/**
* 初始化, 将所有的NumberToolHandler注入到map中
*/
@PostConstruct
private void init() {
handlerMap = handlerList.stream()
.collect(Collectors.toMap(FactoryToolHandler::getToolEnum, Function.identity(), (k1, k2) -> k1));
}
/**
* 根据NumberToolEnum获取对应的NumberToolHandler
*
* @param factoryToolEnum
* @return
*/
public FactoryToolHandler getHandler(FactoryToolEnum factoryToolEnum) {
return MapUtils.emptyIfNull(handlerMap).get(factoryToolEnum);
}
/**
* 获取所有的NumberToolHandler
*
* @return
*/
public List<FactoryToolHandler> getHandlerList() {
return handlerList;
}
}
(三)改进版V2
在改进版V1中,工厂类只是为了获取具体产品实例中的一个而存在。
但是,工厂类存在的意义,不光是为了获取抽象产品的具体实例,即多个产品中的一个。还可以根据不同的需求,
- 对某产品的零件进行组装、加工,生产产品的唯一实例。
因此,可做增加一个工厂类,用于聚合产品,然后统一返回。
Java
@Component
public class AggProductGenerate {
/**
* 生产聚合产品
*/
public List<ModelDto> generateAggProduct(ResolveContext resolveContext, SearchParams searchParams) {
ProductFactory factory = new ProductFactory();
return factory.getProductList().stream().map(product -> {
product.show();
return product.function(resolveContext, searchParams);
}).flatMap(List::stream).collect(Collectors.toList());
}
}
(四)应用
应用代码如下:
Java
@Test
public void simpleFactoryAbstractImplTest() {
// 1. 通过工厂工具类获取产品
System.out.println("productFactory.getProductList() = " + productFactory.getProductList());
Product productA = productFactory.getProduct(ProductEnum.PRODUCT_A);
productA.show();
productA.function(new ResolveContext(), new SearchParams());
Product productB = productFactory.getProduct(ProductEnum.PRODUCT_B);
productB.show();
productB.function(new ResolveContext(), new SearchParams());
// 2. 通过聚合工厂类获取聚合产品
List<ModelDto> modelDtoList = aggProductGenerate.generateAggProduct(new ResolveContext(), new SearchParams());
}
参考资料
版权声明:个人学习记录,本博客所有文章均采用 CC-BY-NC-SA 许可协议。转载请注明出处。
若有侵权,请留言联系~
- 程杰 -《大话设计模式》- 第一章 - p.1-12
- 程序员进阶 -《设计模式 -工厂模式》
- java全栈知识体系 -《设计模式 创建型-简单工厂(Simple Factory)》
如果您觉得文章对您有帮助,请点击文章正下方的小红心一下。您的鼓励是博主的最大动力!