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

前言

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)》

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

相关推荐
大圣数据星球4 小时前
Fluss 写入数据湖实战
大数据·设计模式·flink
思忖小下5 小时前
梳理你的思路(从OOP到架构设计)_设计模式Template Method模式
设计模式·模板方法模式·eit
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭9 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
AskHarries11 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion12 小时前
Springboot的创建方式
java·spring boot·后端
Yvemil713 小时前
《开启微服务之旅:Spring Boot Web开发举例》(一)
前端·spring boot·微服务
星河梦瑾14 小时前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
思忖小下15 小时前
梳理你的思路(从OOP到架构设计)_简介设计模式
设计模式·架构·eit
计算机学长felix15 小时前
基于SpringBoot的“交流互动系统”的设计与实现(源码+数据库+文档+PPT)
spring boot·毕业设计
.生产的驴15 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven