工厂模式和策略模式结合使用的案例介绍

一、前言

在前面的文章中,我们有单独介绍过工厂模式和策略模式,这两种模式是实际开发中经常会用到的,今天来介绍下将两种模式结合起来使用的场景及案例,这种结合的模式也更加的常用,能帮助我们减少if-else的使用的同时,让代码逻辑也清晰简洁、扩展性高。

《工厂模式》

《策略模式》

二、案例

我们假设如下业务场景:

在某CRM系统中,针对不同来源(电话、短信、微信)的客户需要执行各自的名单创建逻辑。

首先,我们新建一个工程,引入相关的依赖信息:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.18</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

然后,我们新建实体类,代表客户的信息:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Customer {

    private String name;
    private Integer age;
    private String address;

}

然后,按照策略模式的样子,我们新建一个抽象类代表公共的策略,然后分别创建手机、短信和微信来源策略:

@Service
public abstract class CommonChannelStrategy {

    /**
     * 定义公共的检查逻辑
     * @param customer 客户信息
     * @return true-检查通过
     */
    public boolean commonCheckValidate(Customer customer) {
        return !ObjectUtils.isEmpty(customer.getName())
            && !ObjectUtils.isEmpty(customer.getAge())
            && !ObjectUtils.isEmpty(customer.getAddress());
    }

    /**
     * 各个渠道特殊的检查逻辑,各自去实现
     * @param customer 客户信息
     * @return true-检查通过
     */
    public abstract boolean specificCheckValidate(Customer customer);

}
@Service
public class TelChannelStrategy extends CommonChannelStrategy{
    /**
     * 电话渠道客户检查策略:必须大于等于18岁才合法
     * @param customer 客户信息
     * @return true-合法
     */
    @Override
    public boolean specificCheckValidate(Customer customer) {
        return !ObjectUtils.isEmpty(customer) && customer.getAge() >= 18;
    }
}
@Service
public class SmsChannelStrategy extends CommonChannelStrategy{
    /**
     * 短信渠道客户检查策略:必须大于等于12岁才合法
     * @param customer 客户信息
     * @return true-合法
     */
    @Override
    public boolean specificCheckValidate(Customer customer) {
        return !ObjectUtils.isEmpty(customer) && customer.getAge() >= 12;
    }
}
@Service
public class WechatChannelStrategy extends CommonChannelStrategy{
    /**
     * 微信渠道客户检查策略:必须大于等于22岁才合法
     * @param customer 客户信息
     * @return true-合法
     */
    @Override
    public boolean specificCheckValidate(Customer customer) {
        return !ObjectUtils.isEmpty(customer) && customer.getAge() >= 22;
    }
}

这些策略如何在合适的时机使用呢?在讲策略模式的时候,我们是借助一个环境类,持有抽象策略的引用,然后初始化该环境类的时候,传进来一个具体策略对象赋值给抽象策略。

这次讲解的是整合工厂模式,使用静态工厂方法,根据入参来从内存中找到早已初始化好的具体策略对象,即枚举中的实例对象。

@AllArgsConstructor
@Getter
public enum ENUM_CUSTOMER_CHANNEL {
    /**
     * 电话
     * 短信
     * 微信
     */
    TEL_CHANNEL("TEL_CHANNEL", "电话来源", "telChannelStrategy"),
    SMS_CHANNEL("SMS_CHANNEL", "短信来源", "smsChannelStrategy"),
    WECHAT_CHANNEL("WECHAT", "微信来源", "wechatChannelStrategy");

    /**
     * 渠道编码
     */
    private final String channelCode;
    /**
     * 渠道名称
     */
    private final String channelName;
    /**
     * 渠道处理方法
     */
    private final String channelService;

}
@Slf4j
public class ChannelFactory {

    /**
     * 存放不同渠道来源名单的处理实例bean
     * telChannelService
     * smsChannelService
     * wechatChannelService
     */
    private static final Map<String, String> SERVICE_BEAN_MAP = new HashMap<>(3);

    // 系统启动时就需要将各个渠道的处理实例bean放到map中
    static {
        for (ENUM_CUSTOMER_CHANNEL channel : ENUM_CUSTOMER_CHANNEL.values()) {
            SERVICE_BEAN_MAP.put(channel.getChannelCode(), channel.getChannelService());
        }
    }

    /**
     * 定义静态工厂方法
     *
     * @param channelCode 渠道编码
     * @return CommonChannelStrategy 具体的处理该渠道客户的实例bean
     */
    public static CommonChannelStrategy getChannelStrategy(String channelCode) {
        String beanName = SERVICE_BEAN_MAP.get(channelCode);
        if (ObjectUtils.isEmpty(beanName)) {
            log.error("渠道类型:{}错误,请检查!", channelCode);
            return null;
        }
        return (CommonChannelStrategy) SpringBeanUtils.getBean(beanName);
    }

}
@Component
public class SpringBeanUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringBeanUtils.applicationContext == null) {
            SpringBeanUtils.applicationContext = applicationContext;
        }
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过name获取Bean
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }
}

如此,我们的策略模式就和静态工厂方法模式整合好了,我们写一个单元测试试一下:

import com.example.aopdemo.AopDemoApplication;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ObjectUtils;

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = AopDemoApplication.class)
public class ChannelFactoryTest {

    @Test
    public void getChannelStrategy() {
        Customer customer = getCustomer();
        CommonChannelStrategy channelStrategy = ChannelFactory.getChannelStrategy("TEL_CHANNEL");
        if(ObjectUtils.isEmpty(channelStrategy)){
            log.info("渠道不合法,无法为{}提供服务!", customer.getName());
        }
        if(!channelStrategy.commonCheckValidate(customer) || !channelStrategy.specificCheckValidate(customer)){
            log.info("该客户{}电话渠道不合法!", customer.getName());
        }
        log.info("该客户{}电话渠道合法!", customer.getName());
    }

    private Customer getCustomer(){
        return Customer.builder()
            .name("张三")
            .age(19)
            .address("上海市")
            .build();
    }
}

运行后成功执行了电话渠道客户策略的行为。

三、总结

为什么要使用这种策略模式和静态工厂方法模式结合的方案呢?

  • 即减少了if-else代码;
  • 可扩展性高了;
  • 避免了自己new对象;
  • 不需要环境类以及新建环境类对象;
  • 大部分复杂业务场景的系统都会选择使用这种方案,比较成熟。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容