1.概述

工厂模式是一种创建型模式,主要作用就是创建对象,将对象的创建过程和使用的过程进行解耦。我们平时说的工厂模式实际上是对三种不同类型的工厂模式的统称,简单工厂、工厂方法、抽象工厂,而在23种设计模式中,只定义了工厂方法和抽象工厂,将简单工厂看作是工厂方法的一种特例,本篇主要讲述的是简单工厂

简单工厂,就像它的名字一样突出一个简单,就是将业务流程代码中直接使用new关键字来创建对象,修改为通过一个工厂类创建对象,这就是简单工厂。如果仅仅只是将new操作转移到了一个新的类里面,看起来只是在徒增类的数量和代码量,并没有什么意义。

那为什么我们还要使用简单工厂模式呢?
是因为它在某些特定的场景下有其存在的价值,让我们从不同的场景来看看。

2.从不同场景看简单工厂的意义

2.1.框架或工具的封装

除了日常业务开发之外,有时候我们也可能得做一些框架或者工具的开发,这部分开发出来的工具需要提供给其他的开发人员使用,而他们在使用的时候,第一步就是获取到这个工具的对象,这种情况下就可以给使用者提供一个简单工厂,让使用者通过工厂来创建对象。
例如在Java中使用日历相关的工具Calendar就是通过简单工厂提供的获取日历对象的方法,如下图:
【设计模式】什么场景可以考虑使用简单工厂模式-LMLPHP
我们做类似的框架或工具开发的时候,完全可以参照Calendar的方式,给使用者提供创建对象的能力,从而让使用者无需关心对象的实际创建过程,而是只需要通过特定的方法和参数,就能获取到一个可供使用的对象。

2.2.复杂业务对象的创建

即使是在做业务相关开发,有时候也会涉及到一些相对复杂的业务对象的创建(例如DDD中的领域对象),这时候可以使用简单工厂将产品对象的创建流程从业务流程中抽离。由于创建对象的复杂性被隔离在工厂类中,因此当涉及到产品类的变化时,比如增加新功能、改变实现方式等,只需要改动工厂类,不会对使用产品的其他模块造成影响,有利于产品创建逻辑的集中管理,以及系统的维护和版本升级。


之前在做一个项目开发的时候使用过简单工厂处理过Domain对象的创建,先说一下背景:

这个项目使用的时COLA架构(一种DDD的代码层面实践的框架),在这个架构的分层中Domain层属于最底层,如下图中的demoWeb-domain
【设计模式】什么场景可以考虑使用简单工厂模式-LMLPHP
Domain层中会完成大部分的业务逻辑,但我们知道业务流程中往往伴随着与中间件其他服务之间的交互(例如数据库的存取操作),但这部分交互不应该由领域对象来实现。

COLA中的做法就是提供一个“防腐层”来实现,所谓的防腐层就是在domain层中定义与中间件交互的interface再交由infrastructure来实现,这样在domain层就只需要关心自己需要做一个什么交互,而不需要关心具体的交互实现,如下的红框所示。
【设计模式】什么场景可以考虑使用简单工厂模式-LMLPHP
背景介绍完了,说一下这里面存在一个问题,就是Domain对象为了保证业务模型的纯洁性,一般不会使用Spring来管理领域对象的生命周期,在这种情况下如何才能将gatewayImpl对象注入到领域对象中呢?

答案是在Infrastructure层中,将getewayImpl对象set到领取对象中,如果领域对象的创建过程相对复杂,就可以使用简单工厂进行创建,统一管理创建逻辑,代码如下:

@Component
public class MsgWecomAppFactory {

    @Resource
    private MsgWecomGateway msgWecomGateway;
    @Resource
    private MsgWecomCacheGateway msgWecomCacheGateway;

    @Resource
    private WecomHttpHelper wecomHttpHelper;

    /**
     * 创建企业微信应用号领域对象,用于确认消息
     *
     * @param agentId 应用号ID
     * @return 领域对象
     */
    public MsgWecomApp createMsgWecomAppForConfirm(String agentId) {
        MsgWecomApp msgWecomApp = new MsgWecomApp();
        // 1.查询企业微信应用配置信息
        Optional<MsgWecomAppConfig> appConfig = msgWecomGateway.getWecomAppConfigByAgentId(agentId);
        Assert.isTrue(appConfig.isPresent(), "企业微信应用号配置信息不存在,执行失败,agentId:" + agentId);
        msgWecomApp.setConfig(appConfig.get());

        msgWecomApp.setMsgWecomCacheGateway(msgWecomCacheGateway);
        msgWecomApp.setMsgWecomGateway(msgWecomGateway);
        msgWecomApp.setWecomHttpHelper(wecomHttpHelper);
        return msgWecomApp;
    }

    /**
     * 创建企业微信应用号领域对象,用于发送消息
     *
     * @param agentId 应用号ID
     * @param phones  手机号
     * @return 领域对象
     */
    public MsgWecomApp createMsgWecomAppForSend(String agentId, Set<String> phones) {
        MsgWecomApp msgWecomApp = new MsgWecomApp();
        // 1.查询企业微信应用配置信息
        Optional<MsgWecomAppConfig> appConfig = msgWecomGateway.getWecomAppConfigByAgentId(agentId);
        Assert.isTrue(appConfig.isPresent(), "企业微信应用号配置信息不存在");
        msgWecomApp.setConfig(appConfig.get());

        // 2.查询用户信息,优先使用手机号查询,如果手机号为空或手机号未查询到则使用邮箱查询
        List<MsgWecomMemberInfo> msgWecomMemberInfos = new ArrayList<>();
        if (CollUtil.isNotEmpty(phones)) {
            msgWecomMemberInfos = msgWecomGateway.listWecomMemberInfoByPhone(phones);
            // 对比参数中的手机号与查询到的手机号,获取差集
            List<String> existPhone = msgWecomMemberInfos.stream().map(MsgWecomMemberInfo::getPhone).collect(Collectors.toList());
            msgWecomApp.setNotExistPhones(CollUtil.subtractToList(phones, existPhone));
        }

        msgWecomApp.setToSendMemberInfos(msgWecomMemberInfos);

        msgWecomApp.setMsgWecomCacheGateway(msgWecomCacheGateway);
        msgWecomApp.setMsgWecomGateway(msgWecomGateway);
        msgWecomApp.setWecomHttpHelper(wecomHttpHelper);
        return msgWecomApp;
    }
}

2.3.与其他模式的组合使用

策略模式中的选择器实现:
SpringBoot优雅使用策略模式这一篇博客中提到了如何使用Spring对Bean的管理能力,来实现策略模式的选择器。同样的,如果没有使用Spring或者业务对象的生命周期不需要Spring框架介入时,就可以使用简单工厂+单例的方式来实现,代码如下:

/**
 * 策略选择器工厂
 */
public class StrategySelectorFactory {

    private static final Map<String, Strategy> STRATEGY_MAP = new java.util.HashMap<>();

    static {
        STRATEGY_MAP.put("A", new StrategyA());
        STRATEGY_MAP.put("B", new StrategyB());
    }

    public static Strategy getStrategy(String strategyKey) {
        if (strategyKey == null || strategyKey.isEmpty()) {
            throw new IllegalArgumentException("strategyKey can not be empty");
        }
        return STRATEGY_MAP.get(strategyKey);
    }
}

3.总结

本篇主要讲述了工厂模式中的特例:简单工厂模式,并通过3种不同的场景来介绍这种模式存在的意义,有以下几方面:

  • 封装对象创建过程: 将对象的创建过程封装工厂类中,使用者无需了解具体产品的创建细节,只需调用工厂类提供的静态方法即可得到所需的产品对象。这样可以隐藏产品类的具体实现,降低耦合度。
  • 控制逻辑集中: 将复杂对象的创建对象逻辑集中在工厂类中,如果需要修改或扩展产品类型时,只需要修改工厂类中的代码。这使得添加新产品或者调整产品创建逻辑更加方便、集中管理,也有利于系统的维护和版本升级
  • 组合其他模式满足特定的需求: 在某些应用场景中,如根据参数动态选择不同类型的对象实例化,简单工厂+单例提供简洁有效的解决方案,避免了直接使用 new 关键字创建对象带来的硬编码问题。

最后,虽然简单工厂模式在一定程度上提高了灵活性和可维护性,但它也有其局限性,例如违反了开闭原则,每增加一个新产品就需要修改工厂类的代码。

但瑕不掩瑜,简单工厂模式在很多简单场景下发挥着重要的作用。

01-19 03:20