简单工厂模式 - “轻松实例化对象的魔法工坊~”

前言

Hi, 我是Rike,欢迎来到我的频道~

本篇为大家带来的是,设计模式-简单工厂模式,它并不属于GoF23个经典设计模式,但通常将它作为学习其他工厂模式的基础,设计思想十分简单精妙。 同时,在文章最后,我们再与Spring框架相结合,方便我们更专注于业务开发。

希望各位多多支持~

一、介绍

简单工厂模式,是一种创建型设计模式,用一个专门的工厂类来统一管理所有产品对象的创建过程。

详细来说,它将某一系列产品的实例化过程,封装到一个工厂类的方法中,根据需求来获取具体对象,而无需直接使用 new 关键字来实例化对象。

一个接口及其所有实现类都属于一类产品,他们属于同一个产品结构。

优缺点如下:

  • 优点:

    • 简化对象的创建。
    • 隐藏了对象创建的内部细节。
    • 可以根据配置灵活创建对象。
  • 缺点:

    • 违反开闭原则:每当新增一个工厂,就需要修改工厂类的代码。
    • 违反单一职责原则:工厂类负责多个对象的创建,可能包含了对象创建的多个逻辑,职责过重。

二、模式原理

在简单工厂模式中,有三个主要角色:

  • 工厂(Factory):核心角色,作为调用者和具体产品实现的中间角色,包含一个或多个创建产品的静态方法。
  • 抽象产品(Abstract Product):定义通用属性和方法,为对外提供访问途径。
  • 具体产品(Concrete Product):实现抽象产品,提供了产品的具体功能和行为(具体实现逻辑)。

工厂类的作用,就是获取产品实例(唯一实例/多例)。

可以理解为根据具体实现类的唯一标识,从工厂类中获取对应实例。

本次示例的类图如下所示:

标识可随需求而定,我采用枚举作为具体产品实现类的唯一标识。

三、应用实现

(一)应用场景

简单工厂模式,适用于以下场景:

  • 完全封装隔离某个具体实现,只能通过对外接口操作封装体。
  • 对外创建对象的职责进行集中管理和控制。

(二)实现步骤

  1. 创建抽象产品接口;
  2. 定义产品实现枚举类;
  3. 创建具体产品实现类;
  4. 创建工厂类;
  5. 应用。

(三)示例代码

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 许可协议。转载请注明出处。

若有侵权,请留言联系~

  1. 程杰 -《大话设计模式》- 第一章 - p.1-12
  2. 程序员进阶 -《设计模式 -工厂模式》
  3. java全栈知识体系 -《设计模式 创建型-简单工厂(Simple Factory)》

如果您觉得文章对您有帮助,请点击文章正下方的小红心一下。您的鼓励是博主的最大动力!

相关推荐
花好月圆春祺夏安24 分钟前
基于odoo17的设计模式详解---代理模式
设计模式·代理模式
仰望星空@脚踏实地1 小时前
Spring Boot Web 服务单元测试设计指南
spring boot·后端·单元测试
一勺菠萝丶3 小时前
Spring Boot + MyBatis/MyBatis Plus:XML中循环处理List参数的终极指南
xml·spring boot·mybatis
RainbowSea4 小时前
问题:后端由于字符内容过长,前端展示精度丢失修复
java·spring boot·后端
风象南4 小时前
SpringBoot 控制器的动态注册与卸载
java·spring boot·后端
我是一只代码狗5 小时前
springboot中使用线程池
java·spring boot·后端
hello早上好5 小时前
JDK 代理原理
java·spring boot·spring
PanZonghui5 小时前
Centos项目部署之运行SpringBoot打包后的jar文件
linux·spring boot
沉着的码农6 小时前
【设计模式】基于责任链模式的参数校验
java·spring boot·分布式
zyxzyx6666 小时前
Flyway 介绍以及与 Spring Boot 集成指南
spring boot·笔记