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


以下使用了 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. Java集合【6】-- Collection和Collections的区别

    刚开始学java的时候,分不清Collection和Collections,其实这两个东西是完全不一样的东西. Collection是一个接口,是java集合中的顶级接口之一,衍生出了java集合的庞 ...

  2. ubuntu配置网络和静态路由(界面配置形式)

    目录 网卡配置 静态ip配置 静态路由 外网ip配置(动态获取DHCP) 内网ip和静态路由配置 本文主要针对ubuntu18.0系统进行界面形式配置网络.并配置静态路由. 网卡配置 静态ip配置 打 ...

  3. gsap基础一[from,to,fromTo]

    学了几天基础了,感觉总算有点入了一个门的感觉啦,gasp不难,想想一年前我看着官网跟天文一样,今年真的进步很大,在外网发现学习的新世界, 自己的获取知识和查看api源码的能力也增强了许多,现在国内的气 ...

  4. python语法元素的名称

    变量 什么是变量? """ 变量是保存和表示数据值的一种语法元素,在程序中十分常见.顾名思义,变量的值是可以改变的,能够通过赋值(使用等号"=")方式 ...

  5. for循环与while循环

    1.两中循环的语法结构 for循环结构: for(表达式1;表达式2;表达式3) { 执行语句; } while循环结构: while(表达式1) { 执行语句; } 2.两者区别: 应用场景:由于f ...

  6. 雪花算法 Java 版

    雪花算法根据时间戳生成有序的 64 bit 的 Long 类型的唯一 ID 各 bit 含义: 1 bit: 符号位,0 是正数 1 是负数, ID 为正数,所以恒取 0 41 bit: 时间差,我们 ...

  7. sql绕过2

    0x00 sql注入理解 SQL注入能使攻击者绕过认证机制,完全控制远程服务器上的数据库. SQL是结构化查询语言的简称,它是访问数据库的事实标准.目前,大多数Web应用都使用SQL数据库来存放应用程 ...

  8. (转)oracle体系结构

    对于一门技术的学习,尤其是像Oracle database这种知识体系极其庞杂的技术来讲,从宏观上了解其体系结构是至关重要的.同时,个人认为,未必是专业DBA人员才需要了解其体系结构(固然对于数据库专 ...

  9. 数组编程题(github每日一题)

    /** * 随机生成一个长度为 10 的整数类型的数组,例如 [2, 10, 3, 4, 5, 11, 10, 11, 20], * 将其排列成一个新数组,要求新数组形式如下,例如 [[2, 3, 4 ...

  10. PHash从0到1

    背景 在重复图识别领域,对于识别肉眼相同图片,PHash是很有用的,而且算法复杂度很低.它抓住了 " 人眼对于细节信息不是很敏感 " 的特性,利用DCT变换把高频信息去掉,再加上合 ...