准备工作:
假设这样的一个业务场景:有一个自动开票的功能需要实现,在程序里面需要根据账单的类型执行对应的处理逻辑。


以下使用了 Lombok 简化代码!!!


账单类型枚举:

/**
* @author ly-az
* @date 12/23/2020 11:34
* 账单类型
*/
public enum BillType { /**
* 账单的类型
*/
BT0001, BT0002 }

账单类:

/**
* @author ly-az
* @date 12/23/2020 11:31
* 账单实体
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Bill { /**
* 账单信息
*/
String message; /**
* 账单类型类型(BT0001、BT0002)
*/
BillType type; }

账单的模拟生成:用于生成一下模拟数据

/**
* @author ly-az
* @date 12/23/2020 11:42
* 生成用于测试的账单
*/
public class BillBuilder {
/**
* 生成账单
*
* @return 账单集合
*/
public static List<Bill> generateBillList() {
val billList = new ArrayList<Bill>();
billList.add(new Bill("我是第一种类型的账单", BillType.BT0001));
billList.add(new Bill("我是第二种类型的账单", BillType.BT0002));
return billList;
}
}

使用if-else来实现的话:

/**
* @author ly-az
* @date 12/24/2020 10:31
* if-else
*/
public class Test01 { public static void main(String[] args) {
List<Bill> billList = BillBuilder.generateBillList();
billList.forEach(bill -> {
if (bill.getType().equals(BillType.BT0001)) {
// 模拟实现业务处理
System.out.println("开始处理BT0001类型的账单 > > > " + bill.getMessage() + ". . . ");
System.out.println("处理成功!!!");
} else if (bill.getType().equals(BillType.BT0002)) {
System.out.println("开始处理BT0002类型的账单 > > > " + bill.getMessage() + ". . . ");
System.out.println("处理成功!!!");
}
// 。。。 未来还有数不清的else if分支 。。。
else {
System.out.println("账单类型不匹配!!!");
}
});
} }

在遇到if-else的分支业务逻辑比较复杂时,为了代码的可读性我们会将其整理成一个方法或者封装成一个工具类去调用,避免整个if-else的结构就显得过于臃肿(这里就模拟了两种类型,偷懒...ψ(`∇´)ψ)。
但是当账单类型越来越多时,if-else分支就会越来越多,每增加一种类型,就需要去修改或添加if-else分支,违反了开闭原则(对扩展开放,对修改关闭)
**

方案一:使用策略模式+Map字典

策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

------《JAVA与模式》

也就是说让我们自己的代码不再依赖于调用业务代码的时的客户端,能够根据不同的客户端执行不同的代码,而客户端只需要调用我们的策略接口即可。


在上述场景中,可以把 if-else 分支的代码抽取为各种不同的策略实现,而且我们可以把选择不同策略的实现的代码逻辑抽取到一个工厂类中去,这就是策略模式+简单工厂模式,为了进一步简化,我们还可以把策略的实现放到一个Map中封装成字典


看代码:

  • 抽象策略:通常由一个接口或抽象类实现。是所有的具体策略类所需的接口。
/**
* @author ly-az
* @date 12/23/2020 11:46
* @see BillHandleStrategyFactory 策略工厂
* 账单处理的策略接口<br>
* 有新的类型的账单时只需要实现这个接口重写接口里的实现方法并添加到策略工厂里面的字典里即可
*/
public interface BillHandleStrategy {
/**
* 处理方法
*
* @param bill 账单
*/
void handleBill(Bill bill);
}
  • 环境:持有一个策略接口的引用。
/**
* @author ly-az
* @date 12/23/2020 11:46
*/
public class BillStrategyContext { private BillHandleStrategy billHandleStrategy; /**
* 设置策略接口
*
* @param billHandleStrategy 策略接口
*/
public void setBillHandleStrategy(BillHandleStrategy billHandleStrategy) {
this.billHandleStrategy = billHandleStrategy;
} public void handleBill(Bill bill) {
if (billHandleStrategy != null) {
billHandleStrategy.handleBill(bill);
}
}
}
  • 具体的策略实现:就是处理账单的策略接口的具体实现,账单对应的处理者。
/**
* @author ly-az
* @date 12/23/2020 13:01
* 处理BT0001账单的策略接口的实现类
*/
public class Bt0001BillHandleStrategy implements BillHandleStrategy { @Override
public void handleBill(Bill bill) {
// 模拟业务处理
System.out.println("开始处理BT0001类型的账单 > > > " + bill.getMessage() + ". . .");
System.out.println("处理成功!!!");
} }
/**
* @author ly-az
* @date 12/23/2020 13:02
* 处理BT0002类型的策略实现类
*/
public class Bt0002BillHandleStrategy implements BillHandleStrategy { @Override
public void handleBill(Bill bill) {
// 模拟实现业务处理
System.out.println("开始处理BT0002类型的账单 > > > " + bill.getMessage() + ". . . ");
System.out.println("处理成功!!!");
}
}
  • 策略工厂:用于获取具体策略的实现类对象
/**
* @author ly-az
* @date 12/23/2020 13:05
* 策略工厂
*/
public class BillHandleStrategyFactory { private static final Map<BillType, BillHandleStrategy> BILL_HANDLE_STRATEGY_MAP; static {
// 初始化,
BILL_HANDLE_STRATEGY_MAP = new HashMap<>();
BILL_HANDLE_STRATEGY_MAP.put(BillType.BT0001, new Bt0001BillHandleStrategy());
BILL_HANDLE_STRATEGY_MAP.put(BillType.BT0002, new Bt0002BillHandleStrategy());
} /**
* 根据账单类型直接从Map字典里获取对应的处理策略实现类
*
* @param billType 账单类型
* @return 处理策略的实现类
*/
public static BillHandleStrategy getBillHandleStrategy(BillType billType) {
return BILL_HANDLE_STRATEGY_MAP.get(billType);
}
}


测试代码:**

/**
* @author ly-az
* @date 12/23/2020 13:12
*/
public class Test02 { public static void main(String[] args) {
//模拟账单
List<Bill> billList = BillBuilder.generateBillList();
//策略上下文
BillStrategyContext billStrategyContext = new BillStrategyContext();
billList.forEach(bill -> {
//获取并设置策略
BillHandleStrategy billHandleStrategy = BillHandleStrategyFactory.getBillHandleStrategy(bill.getType());
billStrategyContext.setBillHandleStrategy(billHandleStrategy);
//执行策略
billStrategyContext.handleBill(bill);
});
}
}


测试结果:


每当有一种新的账单类型,只需要添加新的账单处理策略,并添加到BillHandleStrategyFactory中的Map集合。
如果要使得程序符合开闭原则,则需要调整BillHandleStrategyFactory中处理策略的获取方式。
(好吧,我摊牌了,其实是我太菜了没有想到怎么写ヾ(•ω•`)o)

改进思路:策略模式+注解,通过自定义注解,使用注解标记具体的策略实现类,我们就可以通过反射获取到具体的策略实现类的实例,然后放置到容器的Map字典中!

方案二:使用责任链模式

顾名思义,责任链模式(Chain of Responsibility Pattern)为调用请求创建了一个接收者对象的链。让多个对象都有可能接收请求,并将这些对象连接成一条链,并且沿着这条链传递调用请求,直到有对象处理它为止。这种类型的设计模式也属于行为型模式。
发出这个调用请求的客户端并不知道链上的哪一个对象会最终处理这个调用请求,这使得我们的程序可以在不影响客户端的情况下动态地重新组织和分配责任。

  • 账单处理的抽像接口
/**
* @author ly-az
* @date 12/23/2020 14:51
* 账单的抽象处理接口
*/
public interface IBillHandler { /**
* 利用责任链处理账单的抽象方法
* @param bill 账单
* @param handleChain 处理链
*/
void handleBill(Bill bill, IBillHandleChain handleChain); }
  • 账单处理的的具体实现
/**
* @author ly-az
* @date 12/23/2020 15:14
* 账单的具体处理者的实现
*/
public class Bt0001BillHandler implements IBillHandler { @Override
public void handleBill(Bill bill, IBillHandleChain handleChain) {
if (BillType.BT0001.equals(bill.getType())) {
// 模拟实现业务处理
System.out.println("开始处理BT0001类型的账单 > > > " + bill.getMessage() + ". . . ");
System.out.println("处理成功!!!");
}
//处理不了该回执就往下传递
else {
handleChain.handleBill(bill);
}
}
}
/**
* @author ly-az
* @date 12/23/2020 15:42
* todo
*/
public class Bt0002BillHandler implements IBillHandler { @Override
public void handleBill(Bill bill, IBillHandleChain handleChain) {
if (BillType.BT0002.equals(bill.getType())) {
// 模拟实现业务处理
System.out.println("开始处理BT0002类型的账单 > > > " + bill.getMessage() + ". . . ");
System.out.println("处理成功!!!");
}
//处理不了该回执就往下传递
else {
handleChain.handleBill(bill);
}
}
}
  • 责任链接口
/**
* @author ly-az
* @date 12/23/2020 14:52
* 责任链接口
*/
public interface IBillHandleChain {
/**
* 处理账单的抽象方法
*
* @param bill 账单
*/
void handleBill(Bill bill);
}
  • 责任链接口的实现
/**
* @author ly-az
* @date 12/23/2020 15:08
* 处理账单的责任链
*/
public class BillHandleChain implements IBillHandleChain {
/**
* 记录当前处理者位置
*/
private int index = 0;
/**
* 处理者集合
*/
private static final List<IBillHandler> BILL_HANDLER_LIST; static {
//从容器中获取处理器对象
BILL_HANDLER_LIST = BillHandlerContext.getBillHandlerList();
} @Override
public void handleBill(Bill bill) {
if (BILL_HANDLER_LIST != null && BILL_HANDLER_LIST.size() > 0) {
if (index != BILL_HANDLER_LIST.size()) {
IBillHandler billHandler = BILL_HANDLER_LIST.get(index++);
billHandler.handleBill(bill, this);
}
}
}
}
  • 责任链处理者容器(如果项目中有Spring的IOC容器 ,则可以直接通过依赖注入的方式获取到IBillHandler 的具体实现)
/**
* @author ly-az
* @date 12/23/2020 15:43
* 处理容器
*/
public class BillHandlerContext { private BillHandlerContext() {
} public static List<IBillHandler> getBillHandlerList() {
val billHandlerList = new ArrayList<IBillHandler>();
billHandlerList.add(new Bt0001BillHandler());
billHandlerList.add(new Bt0002BillHandler());
return billHandlerList;
} }
  • 测试用例
/**
* @author ly-az
* @date 12/23/2020 15:48
* 责任链模式下的测试
*/
public class TestClient2 { public static void main(String[] args) {
List<Bill> billList = BillBuilder.generateBillList();
billList.forEach(bill -> {
//回执处理链的实例对象
BillHandleChain billHandleChain = new BillHandleChain();
billHandleChain.handleBill(bill);
});
} }

结果:



同样,如果要使得程序符合开闭原则,则需要调整BillHandlerContext中处理者的获取方式,通过反射的方式,获取指定包下的所有IBillHandler的实现类。
**
o( ̄▽ ̄)ブ,我想到了这种解决方案下该如何改造使其符合开闭原则!!!


我们需要引入一个反射用的工具类:

/**
* @author ly-az
* @date 12/23/2020 16:00
* 反射工具类
*/
public class ReflectionUtil { /**
* 定义类集合(用于存放所有加载的类的镜像)
*/
private static final Set<Class<?>> CLASS_SET; static {
//指定加载包路径
CLASS_SET = getClassSet("com.az");
} /**
* 获取类加载器
*
* @return 类加载器
*/
public static ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
} /**
* 加载类
*
* @param className 类全限定名称
* @param isInitialized 是否在加载完成后执行静态代码块
* @return 类的镜像
*/
public static Class<?> loadClass(String className, boolean isInitialized) {
Class<?> cls;
try {
cls = Class.forName(className, isInitialized, getClassLoader());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return cls;
} public static Class<?> loadClass(String className) {
return loadClass(className, true);
} /**
* 获取指定包下所有类
*
* @param packageName 全限包名
* @return 镜像的Set集合
*/
public static Set<Class<?>> getClassSet(String packageName) {
Set<Class<?>> classSet = new HashSet<>();
try {
Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (url != null) {
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
String packagePath = url.getPath().replace("%20", "");
addClass(classSet, packagePath, packageName);
} else if ("jar".equals(protocol)) {
JarURLConnection jarUrlConnection = (JarURLConnection) url.openConnection();
if (jarUrlConnection != null) {
JarFile jarFile = jarUrlConnection.getJarFile();
if (jarFile != null) {
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String jarEntryName = jarEntry.getName();
if (jarEntryName.endsWith(".class")) {
String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
doAddClass(classSet, className);
}
}
}
}
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return classSet;
} private static void doAddClass(Set<Class<?>> classSet, String className) {
Class<?> cls = loadClass(className, false);
classSet.add(cls);
} private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
final File[] fileList = new File(packagePath).listFiles(file -> (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory());
assert fileList != null;
Arrays.asList(fileList).forEach(file -> {
String fileName = file.getName();
if (file.isFile()) {
String className = fileName.substring(0, fileName.lastIndexOf("."));
if (StringUtils.isNotEmpty(packageName)) {
className = packageName + "." + className;
}
doAddClass(classSet, className);
} else {
String subPackagePath = fileName;
if (StringUtils.isNotEmpty(packagePath)) {
subPackagePath = packagePath + "/" + subPackagePath;
}
String subPackageName = fileName;
if (StringUtils.isNotEmpty(packageName)) {
subPackageName = packageName + "." + subPackageName;
}
addClass(classSet, subPackagePath, subPackageName);
}
});
} public static Set<Class<?>> getClassSet() {
return CLASS_SET;
} /**
* 获取应用包名下某父类(或接口)的所有子类(或实现类)
*
* @param superClass 父类(或接口)镜像
* @return 所有子类(或实现类)的镜像集合
*/
public static Set<Class<?>> getClassSetBySuper(Class<?> superClass) {
Set<Class<?>> classSet = new HashSet<>();
for (Class<?> cls : CLASS_SET) {
if (superClass.isAssignableFrom(cls) && !superClass.equals(cls)) {
classSet.add(cls);
}
}
return classSet;
} /**
* 获取应用包名下带有某注解的类
*
* @param annotationClass 注解的镜像
* @return 镜像集合
*/
public static Set<Class<?>> getClassSetByAnnotation(Class<? extends Annotation> annotationClass) {
Set<Class<?>> classSet = new HashSet<>();
for (Class<?> cls : CLASS_SET) {
if (cls.isAnnotationPresent(annotationClass)) {
classSet.add(cls);
}
}
return classSet;
} }

对责任链模式的处理容器修改一下:

/**
* @author ly-az
* @date 12/23/2020 15:43
* 处理容器
*/
public class BillHandlerContext { private BillHandlerContext() {
} public static List<IBillHandler> getBillHandlerList() {
val billHandlerList = new ArrayList<IBillHandler>();
//获取IBillHandler接口的实现类
Set<Class<?>> classList = ReflectionUtil.getClassSetBySuper(IBillHandler.class);
if (classList.size() > 0) {
classList.forEach(clazz -> {
try {
billHandlerList.add((IBillHandler) clazz.getDeclaredConstructor().newInstance());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
});
}
return billHandlerList;
} }

至此,责任链方案就符合开闭原则,如果新增一个账单类型,只需要添加一个新的账单处理处理器的实现类即可,无需做其它改动。

小结

if-else以及switch-case 这种分支判断的方式对于分支逻辑不多的简单业务,还是更加直观高效的。但是对于业务复杂,分支逻辑多的情况下,采用一下适当的设计模式,会让代码更加清晰,容易维护,但同时类或方法数量也是倍增的。我们需要对业务做好充分分析,避免一上来就设计模式,避免过度设计!

利用设计模式消除业务代码中的 if-else的更多相关文章

  1. JVM 性能调优实战之:使用阿里开源工具 TProfiler 在海量业务代码中精确定位性能代码

    本文是<JVM 性能调优实战之:一次系统性能瓶颈的寻找过程> 的后续篇,该篇介绍了如何使用 JDK 自身提供的工具进行 JVM 调优将 TPS 由 2.5 提升到 20 (提升了 7 倍) ...

  2. 使用阿里开源工具 TProfiler 在海量业务代码中精确定位性能代码 (jvm性能调优)

    技术交流群:233513714 本文是<JVM 性能调优实战之:一次系统性能瓶颈的寻找过程> 的后续篇,该篇介绍了如何使用 JDK 自身提供的工具进行 JVM 调优将 TPS 由 2.5 ...

  3. 策略模式+注解 干掉业务代码中冗余的if else...

    前言: 之前写过一个工作中常见升级模式-策略模式 的文章,里面讲了具体是怎样使用策略模式去抽象现实中的业务代码,今天来拿出实际代码来写个demo,这里做个整理来加深自己对策略模式的理解.   一.业务 ...

  4. 【译】利用Lombok消除重复代码

    当你在写Getter和Setter时,一定无数次的想过,为什么会有POJO这么烂的东西.你不是一个人!(不是骂人-)无数的开发人员花费了大量的时间来写这种样板代码,而他们本来可以利用这些时间做出更有价 ...

  5. 业务代码中(java class)中如何实现多线程,并且将子线程中的值随方法返回返回值

    转载自http://bbs.csdn.net/topics/390731832 问题: public static String getAddress(final InputStream inputS ...

  6. ifeve.com 南方《JVM 性能调优实战之:使用阿里开源工具 TProfiler 在海量业务代码中精确定位性能代码》

    https://blog.csdn.net/defonds/article/details/52598018 多次拉取 JStack,发现很多线程处于这个状态:    at jrockit/vm/Al ...

  7. 《设计模式面试小炒》策略和工厂模式替代业务场景中复杂的ifelse

    <设计模式面试小炒>策略和工厂模式替代业务场景中复杂的ifelse 我是肥哥,一名不专业的面试官! 我是囧囧,一名积极找工作的小菜鸟! 囧囧表示:小白面试最怕的就是面试官问的知识点太笼统, ...

  8. 朱晔的互联网架构实践心得S2E2:写业务代码最容易掉的10种坑

    我承认,本文的标题有一点标题党,特别是写业务代码,大家因为没有足够重视一些细节最容易调的坑(侧重Java,当然,本文说的这些点很多是不限制于语言的). 1.客户端的使用 我们在使用Redis.Elas ...

  9. 使用#include消除重复代码

    消除重复代码代码很多种,比如: 1)提炼成函数复用 2)使用宏 3)继承 4)使用闭包(boost::bind.boost::function) 上述是最为常用的,对于C++程序,闭包可能用得相对少一 ...

随机推荐

  1. selenium如何处理H5视频

    selenium处理H5视频主要使用的是javascript,javascript函数有内置的对象叫arguments,arguments包含了调用的参数组,[0]代表取第一个值. currentSr ...

  2. pycharm2020激活破解和汉化

    一:破解补丁和程序下载:链接:https://pan.baidu.com/s/1u-aZrKMmfRBlQHtcivUt8Q    提取码:tvko 二:破解步骤: 1.安装下载的pycharm202 ...

  3. 题解 洛谷 P2612 【[ZJOI2012]波浪】DP+高精

    题目描述 题目传送门 分析 因为有绝对值不好处理,所以我们强制从小到大填数 设 \(f[i][j][p][o]\) 为当前填到了第 \(i\) 个数,波动强度为 \(j\),有 \(p\) 个连续段并 ...

  4. MAT内存分析工具安装指南(MAT)

    https://blog.csdn.net/mahl1990/article/details/79298616

  5. 【SHOI2008】JZOJ2020年9月5日提高组 循环的债务

    CSP-2020倒计时:36天 [SHOI2008]JZOJ2020年9月5日提高组 循环的债务 题目 Description Alice.Bob和Cynthia总是为他们之间混乱的债务而烦恼,终于有 ...

  6. 安装pillow报错处理

    sudo python3 pip install pillow 报错:The headers or library files could not be found for jpeg,... 解决:安 ...

  7. Java解决大文件读取的内存问题以及文件流的比较

    Java解决大文件读取的内存问题以及文件流的比较 传统方式 读取文件的方式一般是是从内存中读取,官方提供了几种方式,如BufferedReader, 以及InputStream 系列的,也有封装好的如 ...

  8. PyQt(Python+Qt)学习随笔:枚举类QTreeWidgetItem.ItemType、QListWidgetItem.ItemType的取值及含义

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在Model/View的便利类QTreeWidget.QListWidgetItem中的项类型分别是 ...

  9. 第15.14节 PyQt(Python+Qt)入门学习:Designer的Buttons按钮详解

    一.引言 Qt Designer中的Buttons部件包括Push Button(常规按钮.一般称按钮).Tool Button(工具按钮).Radio Button(单选按钮).Check Box( ...

  10. 开源脉冲神经网络深度学习框架——惊蛰(SpikingJelly)

    开源脉冲神经网络深度学习框架--惊蛰(SpikingJelly) 背景 近年来神经形态计算芯片发展迅速,大量高校企业团队跟进,这样的芯片运行SNN的能效比与速度都超越了传统的通用计算设备.相应的,神经 ...