Java使用不同方式优雅拆分业务逻辑
如何处理复杂的业务逻辑
在实际的业务开发当中,经常会遇到复杂的业务逻辑,可能实现出来的代码并没有什么问题,但是代码的可读性很差。
那么在实际开发中如何避免大面积的 if-else 代码块的问题?
补充说明一点,不是说 if-else 不好,而是多层嵌套的 if-else 导致代码可读性差、维护成本高等问题。
拆分逻辑单元最小化
把业务代码拆分成多段逻辑,提取通用的代码块,核心思想就是逻辑单元最小化,然后合并逻辑单元。
逻辑封装到枚举类中
利用“枚举”来优化复杂的多重业务判断代码,将业务中的逻辑封装到枚举类中。
public enum TimeEnum {
AM("am", "上午") {
@Override
public void setCity(TestCodeData data) {
}
},
PM("pm", "下午") {
@Override
public void setCity(TestCodeData data) {
}
},
DAY("day", "全天") {
@Override
public void setCity(TestCodeData data) {
}
};
public abstract void setCity(TestCodeData data);
private String code;
private String desc;
TimeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
// 使用方式
TimeEnum.AM.setCity(data); // 固定code:am
TimeEnum.valueOf("am").setCity(data); // 动态传入code:am
利用函数式接口优化
业务场景描述:比如让你做一个简单的营销拉新活动,这个活动投放到不同的渠道,不同渠道过来的用户奖励不一样。
假设在头条、微信等渠道都投放了该活动。此时你的代码可能会写出如下形式:
@RestController
@RequestMapping("/activity")
public class ActivityController {
@Resource
private AwardService awardService;
@PostMapping("/reward")
public void reward(String userId, String source) {
if ("toutiao".equals(source)) {
awardService.toutiaoReward(userId);
} else if ("wx".equals(source)) {
awardService.wxReward(userId);
}
}
}
@Service
public class AwardService {
private static final Logger log = LoggerFactory.getLogger(AwardService.class);
public Boolean toutiaoReward(String userId) {
log.info("头条渠道用户{}奖励50元红包!", userId);
return Boolean.TRUE;
}
public Boolean wxReward(String userId) {
log.info("微信渠道用户{}奖励100元红包!", userId);
return Boolean.TRUE;
}
}
看完这段代码,逻辑上是没有什么问题的。但它有一个隐藏的缺陷,如果后期又增加很多渠道的时候,你该怎么办?继续 else if 吗?
我们可以利用函数式接口优化,当然设计模式也可以优化。这里我只是举例使用一下函数式接口的使用方式。
@RestController
@RequestMapping("/activity")
public class ActivityController {
@Resource
private AwardService awardService;
@PostMapping("/reward")
public void reward(String userId, String source) {
awardService.getRewardResult(userId, source);
}
}
@Service
public class AwardService {
private static final Logger log = LoggerFactory.getLogger(AwardService.class);
private Map<String, BiFunction<String, String, Boolean>> sourceMap = new HashMap<>();
@PostConstruct
private void dispatcher() {
sourceMap.put("wx", (userId, source) -> this.wxReward(userId));
sourceMap.put("toutiao", (userId, source) -> this.toutiaoReward(userId));
}
public Boolean getRewardResult(String userId, String source) {
BiFunction<String, String, Boolean> result = sourceMap.get(source);
if (null != result) {
return result.apply(userId, source);
}
return Boolean.FALSE;
}
private Boolean toutiaoReward(String userId) {
log.info("头条渠道用户{}奖励50元红包!", userId);
return Boolean.TRUE;
}
private Boolean wxReward(String userId) {
log.info("微信渠道用户{}奖励100元红包!", userId);
return Boolean.TRUE;
}
}
针对一些复杂的业务场景,业务参数很多时,可以利用@FunctionalInterface 自定义函数式接口来满足你的业务需求,使用原理和本例并无差别。
工厂模式+抽象类
还是以上面的营销拉新活动为例来说明如何使用。
定义抽象业务接口:
public abstract class AwardAbstract {
public abstract Boolean award(String userId);
}
定义具体业务实现类:
// 头条渠道发放奖励业务
public class TouTiaoAwardService extends AwardAbstract {
@Override
public Boolean award(String userId) {
log.info("头条渠道用户{}奖励50元红包!", userId);
return Boolean.TRUE;
}
}
// 微信渠道发放奖励业务
public class WeChatAwardService extends AwardAbstract {
@Override
public Boolean award(String userId) {
log.info("微信渠道用户{}奖励100元红包!", userId);
return Boolean.TRUE;
}
}
利用工厂模式获取实例对象:
public class AwardFactory {
public static AwardAbstract getAwardInstance(String source) {
if ("toutiao".equals(source)) {
return new TouTiaoAwardService();
} else if ("wx".equals(source)) {
return new WeChatAwardService();
}
return null;
}
}
业务入口根据不同渠道执行不同的发放逻辑:
@PostMapping("/reward")
public void reward(String userId, String source) {
AwardAbstract instance = AwardFactory.getAwardInstance(source);
if (null != instance) {
instance.award(userId);
}
}
策略模式
还是以营销拉新为业务场景来说明
定义策略业务接口:
/**
* 策略业务接口
*/
public interface AwardStrategy {
/**
* 奖励发放接口
*/
Boolean award(String userId);
}
定义具体业务实现类:
@Service("toutiao")
public class TouTiaoAwardService implements AwardStrategy {
/**
* 头条渠道发放奖励业务
*/
@Override
public Boolean award(String userId) {
log.info("头条渠道用户{}奖励50元红包!", userId);
return Boolean.TRUE;
}
}
@Service("wx")
public class WeChatAwardService implements AwardStrategy {
/**
* 微信渠道发放奖励业务
*/
@Override
public Boolean award(String userId) {
log.info("微信渠道用户{}奖励100元红包!", userId);
return Boolean.TRUE;
}
}
业务入口根据不同渠道执行不同的发放逻辑:
@Autowired
private ApplicationContext applicationContext;
@GetMapping("award")
public void award(@RequestParam("type") String type, @RequestParam("userId") String userId) {
AwardStrategy service = applicationContext.getBean(type, AwardStrategy.class);
service.award(userId);
}
策略模式+模板方法+工厂模式+单例模式
还是以营销拉新为业务场景来说明,这个业务流程再增加一些复杂度,比如发放奖励之前要进行身份验证、风控验证等一些列的校验,此时你的业务流程该如何实现更清晰简洁呢?
定义业务策略接口:
/** 策略业务接口 */
public interface AwardStrategy {
/**
* 奖励发放接口
*/
Map<String, Boolean> awardStrategy(String userId);
/**
* 获取策略标识,即不同渠道的来源标识
*/
String getSource();
}
定义奖励发放模板流程:
public abstract class BaseAwardTemplate {
private static final Logger log = LoggerFactory.getLogger(BaseAwardTemplate.class);
//奖励发放模板方法
public Boolean awardTemplate(String userId) {
this.authentication(userId);
this.risk(userId);
return this.awardRecord(userId);
}
//身份验证
protected void authentication(String userId) {
log.info("{} 执行身份验证!", userId);
}
//风控
protected void risk(String userId) {
log.info("{} 执行风控校验!", userId);
}
//执行奖励发放
protected abstract Boolean awardRecord(String userId);
}
实现不同渠道的奖励业务:
@Slf4j
@Service
public class ToutiaoAwardStrategyService extends BaseAwardTemplate implements AwardStrategy {
/**
* 奖励发放接口
*/
@Override
public Boolean awardStrategy(String userId) {
return super.awardTemplate(userId);
}
@Override
public String getSource() {
return "toutiao";
}
/**
* 具体的业务奖励发放实现
*/
@Override
protected Boolean awardRecord(String userId) {
log.info("头条渠道用户{}奖励50元红包!", userId);
return Boolean.TRUE;
}
}
@Slf4j
@Service
public class WeChatAwardStrategyService extends BaseAwardTemplate implements AwardStrategy {
/**
* 奖励发放接口
*/
@Override
public Boolean awardStrategy(String userId) {
return super.awardTemplate(userId);
}
@Override
public String getSource() {
return "wx";
}
/***
* 具体的业务奖励发放实现
*/
@Override
protected Boolean awardRecord(String userId) {
log.info("微信渠道用户{}奖励100元红包!", userId);
return Boolean.TRUE;
}
}
定义工厂方法,对外统一暴露业务调用入口:
@Component
public class AwardStrategyFactory implements ApplicationContextAware {
private final static Map<String, AwardStrategy> MAP = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, AwardStrategy> beanTypeMap = applicationContext.getBeansOfType(AwardStrategy.class);
beanTypeMap.values().forEach(strategyObj -> MAP.put(strategyObj.getSource(), strategyObj));
}
/**
* 对外统一暴露的工厂方法
*/
public Boolean getAwardResult(String userId, String source) {
AwardStrategy strategy = MAP.get(source);
if (Objects.isNull(strategy)) {
throw new RuntimeException("渠道异常!");
}
return strategy.awardStrategy(userId);
}
/**
* 静态内部类创建单例工厂对象
*/
private static class CreateFactorySingleton {
private static AwardStrategyFactory factory = new AwardStrategyFactory();
}
public static AwardStrategyFactory getInstance() {
return CreateFactorySingleton.factory;
}
}
业务入口方法:
@RestController
@RequestMapping("/activity")
public class ActivityController {
@PostMapping("/reward")
public void reward(String userId, String source) {
AwardStrategyFactory.getInstance().getAwardResult(userId, source);
}
}
总结寄语
设计模式对于 if-else 的优化,个人觉得有些重,但是也是一种优化方式。
设计模式适合使用在大的业务流程和场景中使用,针对代码块中的 if-else 逻辑优化不推荐使用。
不论使用那种技巧,首先是我们在业务代码开发过程中一定要多思考,将复杂的业务逻辑能通过简洁的代码表现出来,这才是你的核心能力之一,而不是一个 curd boy。
Java使用不同方式优雅拆分业务逻辑的更多相关文章
- SpringBoot自定义异常,优雅解决业务逻辑中的错误
概要 你是不是在为业务逻辑中出现的异常弄的焦头烂额,常常在后台报错,前端却无法提示错误内容,导致用户体验极差?比如下单失败,前端只能提示下单失败,但是却不知道为什么失败,是库存不足,还是余额不足,亦或 ...
- 减少存储过程封装业务逻辑-web开发与传统软件开发的思维模式不同
本篇文章讨论并不是:不要使用存储过程,因为有些事情还是要存储过程来完成,不可能不用.而是关于:"业务逻辑是不是要封装在存储过程中实现,这样子php.java等就是调用存储过程". ...
- Java Swing项目专栏之项目业务流程与业务逻辑
Java Swing项目专栏 项目前言 这个超市管理项目是从八月初开始的,原以为像我这样的小菜比是完全掌控不了这样的项目的.原因是因为大一大二还是没怎么好好学自己的专业课,这次项目做完,我给自己建立了 ...
- java中异常处理机制 throw抛出自定义业务逻辑异常 throws继续抛出 catch捕获后会自动继续抛向调用方法
package com.swift; public class Exception_TestC { public static void main(String[] args) { /* * 第5题: ...
- java秀发入门到优雅秃头路线导航【教学视频+博客+书籍整理】
目录 一.Java基础 二.关于JavaWeb基础 三.关于数据库 四.关于ssm框架 五.关于数据结构与算法 六.关于开发工具idea 七.关于项目管理工具Mawen.Git.SVN.Gradle. ...
- Java中如何更优雅的处理空值
经常看到项目中存在到处空值判断的情况,这些判断,会让人觉得摸不着头绪,它的出现很有可能和当前的业务逻辑并没有关系.但它会让你很头疼.有时候,更可怕的是系统因为这些空值的情况,会抛出空指针异常,导致业务 ...
- Java 技术栈中间件优雅停机方案设计与实现全景图
欢迎关注公众号:bin的技术小屋,阅读公众号原文 本系列 Netty 源码解析文章基于 4.1.56.Final 版本 本文概要 在上篇文章 我为 Netty 贡献源码 | 且看 Netty 如何应对 ...
- MyBatis知多少(6)表现层与业务逻辑层
表现层 表现层负责向最终用户展示应用程序的控制方式以及数据.它还要负责所有信息的布局和格式.今天,商业应用程序最流行的表现方式应该算是Web前端了,它使用HTML和JavaScript并通 过Web浏 ...
- 如何利用动态URL提升SEO及处理业务逻辑
如果你正在建设一个新网站或者对现有网站重新设计,我们认为应该将网站的 URL 转换为用户友好的 URL,或搜索引擎友好的 URL,这类 URL 也称为语义 URL(Semantic URL).哪些UR ...
- RxJava系列番外篇:一个RxJava解决复杂业务逻辑的案例
之前写过一系列RxJava的文章,也承诺过会尽快有RxJava2的介绍.无奈实际项目中还未真正的使用RxJava2,不敢妄动笔墨.所以这次还是给大家分享一个使用RxJava1解决问题的案例,希望对大家 ...
随机推荐
- Linux中的touch命令
Linux中一个文件有3种时间属性,分别是mtime,ctime,atime: modification time (mtime) 当该文件的『内容数据』变更时,就会升级这个时间!内容数据指的是文件的 ...
- 使用 Docker 部署 WebTop 运行 Linux 系统
1)项目介绍 GitHub:https://github.com/linuxserver/docker-webtop WebTop 它是一个基于 Linux ( Ubuntu 和 Alpine 两种版 ...
- 鸿蒙HarmonyOS实战-Stage模型(ExtensionAbility组件)
一.ExtensionAbility组件 1.概念 HarmonyOS中的ExtensionAbility组件是一种能够扩展系统功能的能力组件.它可以通过扩展系统能力接口,为应用程序提供一些特定的功能 ...
- Linux/Golang/glibC系统调用
Linux/Golang/glibC系统调用 本文主要通过分析Linux环境下Golang的系统调用,以此阐明整个流程 有时候涉略过多,反而遭到质疑~,写点文章证明自己实力也好 Golang系统调用 ...
- 面试题--mysql的数据库优化
mysql的数据库优化 当有人问你如何对数据库进行优化时,很多人第一反应想到的就是 SQL 优化,如何创建索引,如何改写 SQL,他们把数据库优化与 SQL 优化划上了等号. 当然这不能算是完全错误的 ...
- C基本知识
1 C基本数据类型 C基本的数据类型说明: 2 字节序 测试代码: #include <stdio.h> typedef unsigned char *byte_pointer; void ...
- Mesh快连
Mesh快连 一.名词解释 Mesh快连是一种由多个节点组成的网络系统,这些节点可以相互连接,形成一个"网状"的结构. 二.如何使用 有线Mesh: 网络拓扑: 设备版本:3.7. ...
- 使用 openssl 从cer公钥证书中导出公钥pem
使用 openssl 从cer公钥证书中导出公钥pem ---------- "der 公钥证书"转 "base64 公钥证书"openssl x509 -in ...
- JAVA RSA 私钥签名 公钥验证签名 公钥验签
JAVA RSA 私钥签名 公钥验证签名 公钥验签 1.待签名字符串转为byte数组时,一般使用UTF8. 2.将私钥字符串(PKCS8格式)转为PKCS8EncodedKeySpec对象. 3.使用 ...
- ColorEasyDuino上手指南
介绍 ColorEasyDuino是嘉立创推出的一块Aduino开发板(类似物),具有丰富的外设接口:uart.i2c.spi.adc.pwm等:开发板设计参考原型是Arduino Uno,采用的芯片 ...