一、前言
在前面的文章中,我们有单独介绍过工厂模式和策略模式,这两种模式是实际开发中经常会用到的,今天来介绍下将两种模式结合起来使用的场景及案例,这种结合的模式也更加的常用,能帮助我们减少if-else的使用的同时,让代码逻辑也清晰简洁、扩展性高。
二、案例
我们假设如下业务场景:
在某CRM系统中,针对不同来源(电话、短信、微信)的客户需要执行各自的名单创建逻辑。
首先,我们新建一个工程,引入相关的依赖信息:
代码语言:javascript复制<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>
然后,我们新建实体类,代表客户的信息:
代码语言:javascript复制@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
private String name;
private Integer age;
private String address;
}
然后,按照策略模式的样子,我们新建一个抽象类代表公共的策略,然后分别创建手机、短信和微信来源策略:
代码语言:javascript复制@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);
}
代码语言:javascript复制@Service
public class TelChannelStrategy extends CommonChannelStrategy{
/**
* 电话渠道客户检查策略:必须大于等于18岁才合法
* @param customer 客户信息
* @return true-合法
*/
@Override
public boolean specificCheckValidate(Customer customer) {
return !ObjectUtils.isEmpty(customer) && customer.getAge() >= 18;
}
}
代码语言:javascript复制@Service
public class SmsChannelStrategy extends CommonChannelStrategy{
/**
* 短信渠道客户检查策略:必须大于等于12岁才合法
* @param customer 客户信息
* @return true-合法
*/
@Override
public boolean specificCheckValidate(Customer customer) {
return !ObjectUtils.isEmpty(customer) && customer.getAge() >= 12;
}
}
代码语言:javascript复制@Service
public class WechatChannelStrategy extends CommonChannelStrategy{
/**
* 微信渠道客户检查策略:必须大于等于22岁才合法
* @param customer 客户信息
* @return true-合法
*/
@Override
public boolean specificCheckValidate(Customer customer) {
return !ObjectUtils.isEmpty(customer) && customer.getAge() >= 22;
}
}
这些策略如何在合适的时机使用呢?在讲策略模式的时候,我们是借助一个环境类,持有抽象策略的引用,然后初始化该环境类的时候,传进来一个具体策略对象赋值给抽象策略。
这次讲解的是整合工厂模式,使用静态工厂方法,根据入参来从内存中找到早已初始化好的具体策略对象,即枚举中的实例对象。
代码语言:javascript复制@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;
}
代码语言:javascript复制@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);
}
}
代码语言:javascript复制@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);
}
}
如此,我们的策略模式就和静态工厂方法模式整合好了,我们写一个单元测试试一下:
代码语言:javascript复制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对象;
- 不需要环境类以及新建环境类对象;
- 大部分复杂业务场景的系统都会选择使用这种方案,比较成熟。