【日常业务开发】策略+工厂模式优化 if...else判断逻辑
策略模式
策略模式是一种行为设计模式,它定义了一系列算法,并将每个算法封装成独立的类,使得它们可以互相替换。策略模式使得算法的变化独立于使用算法的客户端。
策略模式由三个部分组成:策略接口、具体策略类和环境类。策略接口定义了所有具体策略类都需要实现的方法;具体策略类实现了策略接口,并提供不同的算法实现;环境类持有一个策略对象,并利用策略对象执行具体的算法。
策略模式在以下情况下特别适用:
-
需要根据不同的条件或参数选择不同的算法或策略。例如,在一个电商网站中,根据用户的购买历史、地理位置等信息,选择不同的优惠策略或物流配送策略。
-
需要封装一组相似的算法,以便在不同的情况下使用不同的算法。例如,在一个游戏中,根据不同的敌人类型和玩家状态选择不同的战斗策略。
-
有一组类似的行为,但是可能会发生变化。通过将每个行为封装为一个具体策略类,可以更灵活地添加、删除或修改行为。例如,在一个日程管理应用中,可以根据用户的不同需求设置不同的提醒策略。
-
不希望暴露复杂的条件语句或逻辑判断代码。使用策略模式可以将条件语句封装在具体策略类中,使得代码更加清晰、易于理解和维护。
总的来说,策略模式适用于需要根据不同的条件选择不同算法或策略,并且希望将算法或策略的实现与使用代码解耦的情况。它能够提供灵活性、扩展性和可维护性,使得代码更具可读性和可重用性。
工厂模式
工厂模式是一种创建型设计模式,它提供了一种创建对象的接口,但具体的对象创建过程由子类或子工厂类来决定。通过使用工厂模式,可以将对象的创建和使用解耦,使得代码更加灵活、可扩展和易于维护。
工厂模式在以下情况下特别适用:
-
当需要创建复杂对象或对象之间存在复杂的依赖关系时,可以使用工厂模式来封装对象的创建过程,并隐藏创建细节。
-
当需要创建的对象可能会随着程序的运行时条件变化而变化时,可以使用工厂模式来动态地选择合适的对象创建方式。
-
当希望将对象的创建逻辑集中在一处,以便于维护和管理时,可以使用工厂模式来统一对象的创建过程。
工厂模式有多种不同的变体,包括简单工厂模式、工厂方法模式和抽象工厂模式。简单工厂模式通过一个工厂类来创建对象,工厂方法模式通过子类来创建对象,抽象工厂模式通过一组相关的工厂类来创建对象。
总的来说,工厂模式适用于创建复杂对象、解耦对象的创建和使用、动态选择对象创建方式以及集中管理对象的创建过程等情况。它提供了一种灵活、可扩展和可维护的方式来创建对象。
场景
业务中经常有支付业务,项目刚开始所有业务使用支付宝支付,后面湖南地区需要使用微信支付,然后湖北又需要使用易宝支付,后期可能还有扩展。如果使用if…else逻辑,业务中经常要写的代码:
public static void pay() {
// String fruit = "aliPay";
// String fruit = "yiBaoPay";
String payType = "weiXinPay";
if ("aliPay".equals(payType)){
log.info("===调用支付宝接口发起支付业务===");
} else if ("yiBaoPay".equals(payType)){
log.info("===调用易宝接口发起支付业务===");
} else {
log.info("===调用微信接口发起支付业务===");
}
}
如何优雅的处理消除if…else逻辑,方面后期扩展呢?策略+工厂模式优化
策略+工厂模式优化
利用Spring自动注入的特点处理
支付接口IPay和具体实现类AliPay、WeiXinPay、YiBaoPay
public interface IPay {
String pay();
}
@Service
@Slf4j
public class AliPay implements IPay {
@Override
public String pay() {
log.info("===调用支付宝接口发起支付业务===");
return "调用支付宝接口发起支付业务";
}
}
@Service
@Slf4j
public class WeiXinPay implements IPay {
@Override
public String pay() {
log.info("===调用微信接口发起支付业务===");
return "调用微信接口发起支付业务";
}
}
@Service
@Slf4j
public class YiBaoPay implements IPay {
@Override
public String pay() {
log.info("===调用易宝接口发起支付业务===");
return "调用易宝接口发起支付业务";
}
}
工厂类PayFactory
@Component
public class PayFactory {
/**
* 利用Spring自动注入的特点。Key为bean的名称、value为IPay接口对应的bean实现类
*/
@Autowired
Map<String, IPay> map;
public IPay getInstance(String type) {
return map.get(type);
}
}
@RestController
public class PayController {
@Autowired
private PayFactory payFactory;
@GetMapping("/alipay")
public String aliPay(String type) {
IPay pay = payFactory.getInstance(type);
return pay.pay();
}
}
Key为bean的名称、value为IPay接口对应的bean实现类。对应的业务类型不好扩展
继承InitializingBean+静态工厂方法调用处理
public interface IPay extends InitializingBean {
String pay();
}
@Service
@Slf4j
public class AliPay implements IPay {
@Override
public String pay() {
log.info("===调用支付宝接口发起支付业务===");
return "调用支付宝接口发起支付业务";
}
@Override
public void afterPropertiesSet() throws Exception {
PayFactory.registerHandler("ali", this);
}
}
@Service
@Slf4j
public class WeiXinPay implements IPay {
@Override
public String pay() {
log.info("===调用微信接口发起支付业务===");
return "调用微信接口发起支付业务";
}
@Override
public void afterPropertiesSet() throws Exception {
PayFactory.registerHandler("weixin", this);
}
}
@Service
@Slf4j
public class YiBaoPay implements IPay {
@Override
public String pay() {
log.info("===调用易宝接口发起支付业务===");
return "调用易宝接口发起支付业务";
}
@Override
public void afterPropertiesSet() throws Exception {
PayFactory.registerHandler("yibao", this);
}
}
工厂类PayFactory,暴露注册bean到工厂容器Map对象方法,提供根据type类型查询bean的静态方法
public class PayFactory {
private static final Map<String, IPay> PAY_MAP = new HashMap<>();
/**
* 获取对应实例Bean
* @param type
* @return
*/
public static IPay getInstance(String type) {
return PAY_MAP.get(type);
}
/**
* 注册bean到工厂容器对象
* @param handlerName
* @param pay
*/
public static void registerHandler(String handlerName, IPay pay) {
PAY_MAP.put(handlerName, pay);
}
}
注解+CommandLineRunner+ApplicationContextAware处理/ApplicationListener<ContextRefreshedEvent>
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PayCode {
String value();
String name();
}
public interface IPay {
String pay();
}
@PayCode(value = "alia", name = "支付宝支付")
@Service
@Slf4j
public class AliPay implements IPay {
@Override
public String pay() {
log.info("===调用支付宝接口发起支付业务===");
return "调用支付宝接口发起支付业务";
}
}
@PayCode(value = "weixin", name = "微信支付")
@Service
@Slf4j
public class WeiXinPay implements IPay {
@Override
public String pay() {
log.info("===调用微信接口发起支付业务===");
return "调用微信接口发起支付业务";
}
}
@PayCode(value = "yibao", name = "易宝支付")
@Service
@Slf4j
public class YiBaoPay implements IPay {
@Override
public String pay() {
log.info("===调用易宝接口发起支付业务===");
return "调用易宝接口发起支付业务";
}
}
第一种方式: 工厂类PayFactory实现Spring生命周期函数在run方法中处理被标记的@PayCode的bean对象加入到静态容器Map中:
-
CommandLineRunner:当前Bean被IOC容器装配完成调用run()方法
-
ApplicationContextAware: 获取容器对象ApplicationContext
@Component
public class PayFactory implements CommandLineRunner, ApplicationContextAware {
private ApplicationContext applicationContext;
private static final Map<String, IPay> PAY_MAP = new HashMap<>();
/**
* 获取对应实例Bean
* @param type
* @return
*/
public static IPay getInstance(String type) {
return PAY_MAP.get(type);
}
@Override
public void run(String... args) throws Exception {
Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(PayCode.class);
if (beansWithAnnotation !=null ) {
beansWithAnnotation.forEach((k,v)->{
PayCode payCodeAnn = v.getClass().getAnnotation(PayCode.class);
PAY_MAP.put(payCodeAnn.value(), (IPay)v);
});
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
第二种方式: 实现ApplicationListener<ContextRefreshedEvent>监听事件接口
代替CommandLineRunner, ApplicationContextAware
。
ContextRefreshedEvent事件: ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在ConfigurableApplicationContext接口中使用 refresh()方法来发生。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用。
@Component
public class PayFactory implements ApplicationListener<ContextRefreshedEvent> {
private static final Map<String, IPay> PAY_MAP = new HashMap<>();
/**
* 获取对应实例Bean
* @param type
* @return
*/
public static IPay getInstance(String type) {
return PAY_MAP.get(type);
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(PayCode.class);
if (beansWithAnnotation !=null ) {
beansWithAnnotation.forEach((k,v)->{
PayCode payCodeAnn = v.getClass().getAnnotation(PayCode.class);
PAY_MAP.put(payCodeAnn.value(), (IPay)v);
});
}
}
}
@RestController
public class PayController {
@GetMapping("/alipay")
public String aliPay(String type) {
IPay pay = PayFactory.getInstance(type);
return pay.pay();
}
}
Map+函数式接口代替策略模式
用上了Java8的新特性lambda表达式
- 判断条件放在key中
- 对应的业务逻辑放在value中
这样子写的好处是非常直观,能直接看到判断条件对应的业务逻辑
需求:根据优惠券(资源)类型resourceType和编码resourceId查询派发方式grantType
上代码:
@Service
public class QueryGrantTypeService {
@Autowired
private GrantTypeService grantTypeService;
private Map<String, Function<String, String>> grantTypeMap = new HashMap();
/**
* 初始化业务分派逻辑,代替了if-else部分
* key: 优惠券类型
* value: lambda表达式,最终会获得该优惠券的发放方式
*/
@PostConstruct
public void dispatcherInit() {
grantTypeMap.put("红包", resourceId -> grantTypeService.redPaper(resourceId));
grantTypeMap.put("购物券", resourceId -> grantTypeService.shopping(resourceId));
grantTypeMap.put("qq会员", resourceId -> grantTypeService.QQVip(resourceId));
}
public String getResult(String resourceType, String resourceId) {
// 根据 优惠券类型resourceType、编码resourceId 去查询 发放方式grantType
Function<String, String> result = grantTypeMap.get(resourceType);
if (Objects.nonNull(result)) {
//传入resourceId 执行这段表达式获得String型的grantType
return result.apply(resourceId);
}
return "查询不到该优惠券的发放方式";
}
}
如果单个 if 语句块的业务逻辑有很多行的话,我们可以把这些 业务操作抽出来,写成一个单独的Service,即:
@Service
public class GrantTypeService {
public String redPaper(String resourceId){
//红包的发放方式
return "每周末9点发放";
}
public String shopping(String resourceId){
//购物券的发放方式
return "每周三9点发放";
}
public String QQVip(String resourceId){
//qq会员的发放方式
return "每周一0点开始秒杀";
}
}
入参String resourceId是用来查数据库的,这里简化了,传参之后不做处理
@RestController
public class GrantTypeController {
@Autowired
private QueryGrantTypeService queryGrantTypeService;
@GetMapping("/grantType/{resourceType}/{resourceId}")
public String test(@PathVariable String resourceType, @PathVariable String resourceId){
return queryGrantTypeService.getResult(resourceType, resourceId);
}
}
Map+函数式接口通过Map.get(key)来代替 if-else的业务分派,能够避免策略模式带来的类增多、难以俯视整个业务逻辑的问题。