2026/2/7 2:39:48
网站建设
项目流程
地方旅游网站模板,.net 网站开发权限设计,北京注册公司代理机构排名,湖南网站建设加盟代理在日常开发中#xff0c;我们经常会遇到需要根据不同条件执行不同逻辑的场景#xff0c;导致代码中出现大量的if/else嵌套。这不仅降低了代码的可读性和可维护性#xff0c;还会增加后续扩展的难度。
本文将介绍四种优雅的设计模式来优化这种条件爆炸问题#…在日常开发中我们经常会遇到需要根据不同条件执行不同逻辑的场景导致代码中出现大量的if/else嵌套。这不仅降低了代码的可读性和可维护性还会增加后续扩展的难度。本文将介绍四种优雅的设计模式来优化这种条件爆炸问题1 策略模式01 概念首先我们来看下策略模式的定义。策略模式Strategy Pattern是行为型设计模式之一它定义了一系列算法并将每个算法封装起来使它们可以相互替换。 策略模式使得算法可以独立于使用它们的客户端变化。怎么理解策略模式 软件开发中常常遇到这种情况实现某一个功能有多种算法或者策略我们需要根据环境或者条件的不同选择不同的算法或者策略来完成该功能。这么一看不就是 if…else…的逻辑 图中的实现逻辑非常简单当我们是初学者时这样写没有问题只要能正常运行即可。但随着经验的增长这段代码明显违反了 OOP 的两个基本原则单一职责原则一个类或模块只负责完成一个职责或功能。开闭原则软件实体模块、类、方法等应该“对扩展开放对修改关闭”。由于违反了这两个原则当 if-else 块中的逻辑日益复杂时代码会变得越来越难以维护极容易出现问题。策略模式的设计思路是定义一些独立的类来封装不同的算法每一个类封装一个具体的算法。每一个封装算法的类我们都可以称之为策略 (Strategy) 为了保证这些策略的一致性一般会用一个抽象的策略类来做算法的定义而具体每种算法则对应于一个具体策略类。这种设计思想里包含三个角色Context: 环境类Strategy: 抽象策略类ConcreteStrategy: 具体策略类对应的时序图接下来我们用代码简单演示一下。02 例子步骤 1创建接口publicinterfaceStrategy{publicintdoOperation(intnum1,intnum2);}Strategy 接口定义了一个 doOperation 方法所有具体策略类将实现这个接口以提供具体的操作。步骤 2创建实现相同接口的具体类OperationAdd 是一个具体的策略类实现了加法操作。publicclassOperationAddimplementsStrategy{OverridepublicintdoOperation(intnum1,intnum2){returnnum1num2;}}OperationSubtract 是另一个具体的策略类实现了减法操作。publicclassOperationSubtractimplementsStrategy{OverridepublicintdoOperation(intnum1,intnum2){returnnum1-num2;}}OperationMultiply 是第三个具体的策略类实现了乘法操作。publicclassOperationMultiplyimplementsStrategy{OverridepublicintdoOperation(intnum1,intnum2){returnnum1*num2;}}步骤 3创建上下文类publicclassContext{privateStrategystrategy;publicvoidsetStrategy(Strategystrategy){this.strategystrategy;}publicintexecuteStrategy(intnum1,intnum2){returnstrategy.doOperation(num1,num2);}}Context 类持有一个策略对象的引用并允许客户端设置其策略。它提供了一个 executeStrategy 方法用于执行策略并返回结果。步骤 4使用上下文来看到策略改变时的行为变化publicstaticvoidmain(String[]args){ContextcontextnewContext();context.setStrategy(newOperationAdd());System.out.println(10 5 context.executeStrategy(10,5));context.setStrategy(newOperationSubstract());System.out.println(10 - 5 context.executeStrategy(10,5));context.setStrategy(newOperationMultiply());System.out.println(10 * 5 context.executeStrategy(10,5));}执行结果从上面的例子我们简单总结下策略模式的优缺点1、优点策略模式提供了对“开闭原则”的完美支持用户可以在不修改原有系统的基础上选择算法或行为也可以灵活地增加新的算法或行为。策略模式提供了管理相关的算法族的办法。使用策略模式可以避免使用多重条件转移语句。2、缺点客户端必须知道所有的策略类并自行决定使用哪一个策略类。策略模式将造成产生很多策略类可以通过使用享元模式在一定程度上减少对象的数量。2 SPI 机制01 概念SPI 全称为 Service Provider Interface是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中并由服务加载器读取配置文件加载实现类。这样可以在运行时动态为接口替换实现类。正因此特性我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。02 Java SPI JDBC Driver在JDBC4.0 之前我们开发有连接数据库的时候通常先加载数据库相关的驱动然后再进行获取连接等的操作。// STEP 1: Register JDBC driverClass.forName(com.mysql.jdbc.Driver);// STEP 2: Open a connectionStringurljdbc:xxxx://xxxx:xxxx/xxxx;ConnectionconnDriverManager.getConnection(url,username,password);JDBC4.0之后使用了 Java 的 SPI 扩展机制不再需要用 Class.forName(“com.mysql.jdbc.Driver”) 来加载驱动直接就可以获取 JDBC 连接。接下来我们来看看应用如何加载 MySQL JDBC 8.0.22 驱动首先 DriverManager类是驱动管理器也是驱动加载的入口。/** * Load the initial JDBC drivers by checking the System property * jdbc.properties and then use the {code ServiceLoader} mechanism */static{loadInitialDrivers();println(JDBC DriverManager initialized);}在 Java 中static 块用于静态初始化它在类被加载到 Java 虚拟机中时执行。静态块会加载实例化驱动接下来我们看看loadInitialDrivers 方法。加载驱动代码包含四个步骤1、系统变量中获取有关驱动的定义。2、使用 SPI 来获取驱动的实现类字符串的形式。3、遍历使用 SPI 获取到的具体实现实例化各个实现类。4、根据第一步获取到的驱动列表来实例化具体实现类。我们重点关注 SPI 的用法首先看第二步使用 SPI 来获取驱动的实现类 , 对应的代码是ServiceLoaderDriverloadedDriversServiceLoader.load(Driver.class);这里没有去 META-INF/services目录下查找配置文件也没有加载具体实现类做的事情就是封装了我们的接口类型和类加载器并初始化了一个迭代器。接着看第三步遍历使用SPI获取到的具体实现实例化各个实现类对应的代码如下IteratorDriverdriversIteratorloadedDrivers.iterator();//遍历所有的驱动实现while(driversIterator.hasNext()){driversIterator.next();}在遍历的时候首先调用driversIterator.hasNext()方法这里会搜索 classpath 下以及 jar 包中所有的META-INF/services目录下的java.sql.Driver文件并找到文件中的实现类的名字此时并没有实例化具体的实现类。然后是调用driversIterator.next()方法此时就会根据驱动名字具体实例化各个实现类了现在驱动就被找到并实例化了。这里有一个小小的疑问 那么如果发现了多个 driver 的话要选择哪一个具体的实现呢那就是 JDBC URL 了driver 的实现有一个约定如果 driver 根据 JDBC URL 判断不是自己可以处理的连接就直接返回空DriverManager 就是基于这个约定直到找到第一个不返回 null 的连接为止。JDBC Driver 是 Java SPI 机制的一个非常经典的应用场景包含三个核心步骤定义 SPI 文件 指定 Driver class 全限定名 通过 DriverManager 静态类自动加载驱动 加载之后当需要获取连接时还需要根据 ConnectionUrl 判断使用哪一个加载完成的 Driver 。因此 JDBC Driver 的 SPI 机制是需要多个步骤配合来完成的 同时基于 Java SPI 机制的缺陷同样也无法按需加载。03 按需加载Dubbo SPI 机制基于 Java SPI 的缺陷无法支持按需加载接口实现类Dubbo 并未使用 Java SPI而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中通过 ExtensionLoader我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下配置内容如下optimusPrimeorg.apache.spi.OptimusPrimebumblebeeorg.apache.spi.Bumblebee与 Java SPI 实现类配置不同Dubbo SPI 是通过键值对的方式进行配置这样我们可以按需加载指定的实现类。另外在测试 Dubbo SPI 时需要在 Robot 接口上标注 SPI 注解。下面来演示 Dubbo SPI 的用法publicclassDubboSPITest{TestpublicvoidsayHello()throwsException{ExtensionLoaderRobotextensionLoaderExtensionLoader.getExtensionLoader(Robot.class);RobotoptimusPrimeextensionLoader.getExtension(optimusPrime);optimusPrime.sayHello();RobotbumblebeeextensionLoader.getExtension(bumblebee);bumblebee.sayHello();}}测试结果如下 另外Dubbo SPI 除了支持按需加载接口实现类还增加了 IOC 和 AOP 等特性 。SPI 机制的优势实现完全解耦新增实现无需修改主代码支持运行时动态发现和加载3 责任链模式01 概念责任链模式是一种行为设计模式 允许你将请求沿着处理者链进行发送。 收到请求后 每个处理者均可对请求进行处理 或将其传递给链上的下个处理者。更简单的描述是责任链是一个链表结构这条链上有多个节点它们有相同的接口各自有各自的责任和实现。当有数据输入时第一个节点看自己能否处理问题如果可以就进行处理如果不能就数据交给下一个节点进行处理以此类推直到最后一个责任节点。责任链模式的使用场景是有多个对象可以处理同一个请求具体由哪个对象处理是在运行时确定的。例如有 ABC 三个处理者可以处理一个请求请求 a 需要 AC 两个处理者处理请求 b 需要 BC 两个处理者处理同时两个处理者之间可能有前后依赖关系这时候就可以使用责任链模式在不确定请求处理者的时候向同一类型的处理者中的一个发送请求02 例子支付流程是通过定时任务执行每次都需要去判断用户的账号余额、优惠券、企业账号余额是否可以抵扣假如满足条件则从对应的账号资产里扣除对应的金额 。因为原来的代码存在大量的 if/else 逻辑业务逻辑相互杂糅维护成本很高重构了该逻辑 , 以下是演示伪代码1、定义抽象处理者Handler/** * 支付处理器接口 * 定义所有支付方式需要实现的方法 */publicinterfacePaymentHandler{/** * 判断该支付处理器是否适用于当前用户 * param userId 用户ID * return 是否适用 */booleancanProcess(StringuserId);/** * 执行支付操作 * param userId 用户ID * param amount 支付金额 * return 支付结果 */PaymentResultprocessPayment(StringuserId,doubleamount);}/** * 支付结果类 * 封装支付操作的结果信息 */classPaymentResult{privateboolean success;// 支付是否成功privatedouble paidAmount;// 实际支付金额privateStringmessage;// 支付结果描述信息publicPaymentResult(booleansuccess,doublepaidAmount,Stringmessage){this.successsuccess;this.paidAmountpaidAmount;this.messagemessage;}// Getter方法publicbooleanisSuccess(){returnsuccess;}publicdoublegetPaidAmount(){returnpaidAmount;}publicStringgetMessage(){returnmessage;}}2、定义具体处理者Concrete Handler/** * 企业账户支付处理器实现 */publicclassCorporatePaymentHandlerimplementsPaymentHandler{privatestaticfinaldoubleMAX_CORPORATE_PAYMENT5000;// 企业账户最大支付限额OverridepublicbooleancanProcess(StringuserId){// 只有以emp_开头的用户ID才能使用企业账户支付returnuserId.startsWith(emp_);}OverridepublicPaymentResultprocessPayment(StringuserId,doubleamount){System.out.printf(尝试使用企业账户支付: 用户[%s], 金额[%.2f]%n,userId,amount);if(amountMAX_CORPORATE_PAYMENT){// 金额在限额内全额支付returnnewPaymentResult(true,amount,企业账户支付成功);}else{// 超过限额只支付限额部分returnnewPaymentResult(true,MAX_CORPORATE_PAYMENT,String.format(企业账户部分支付成功(%.2f)还需支付: %.2f,MAX_CORPORATE_PAYMENT,amount-MAX_CORPORATE_PAYMENT));}}}/** * 个人账户支付处理器实现 */publicclassPersonalPaymentHandlerimplementsPaymentHandler{privatestaticfinaldoubleMAX_PERSONAL_PAYMENT1000;// 个人账户最大支付限额OverridepublicbooleancanProcess(StringuserId){// 所有用户都可以使用个人账户支付returntrue;}OverridepublicPaymentResultprocessPayment(StringuserId,doubleamount){System.out.printf(尝试使用个人账户支付: 用户[%s], 金额[%.2f]%n,userId,amount);if(amountMAX_PERSONAL_PAYMENT){// 金额在限额内全额支付returnnewPaymentResult(true,amount,个人账户支付成功);}else{// 超过限额只支付限额部分returnnewPaymentResult(true,MAX_PERSONAL_PAYMENT,String.format(个人账户部分支付成功(%.2f)还需支付: %.2f,MAX_PERSONAL_PAYMENT,amount-MAX_PERSONAL_PAYMENT));}}}3、定义组合支付上下文核心数据结构是链表/** * 组合支付上下文 * 负责管理支付处理器并执行组合支付 */publicclassCompositePaymentContext{privatefinalListPaymentHandlerhandlersnewArrayList();/** * 添加支付处理器 * param handler 支付处理器实例 */publicvoidaddHandler(PaymentHandlerhandler){handlers.add(handler);}/** * 执行组合支付 * param userId 用户ID * param totalAmount 总支付金额 * return 支付结果映射表key为处理器类名value为支付结果 */publicMapString,PaymentResultexecutePayment(StringuserId,doubletotalAmount){// 使用LinkedHashMap保持支付尝试的顺序MapString,PaymentResultpaymentResultsnewLinkedHashMap();doubleremainingtotalAmount;// 剩余待支付金额// 按处理器顺序尝试支付for(PaymentHandlerhandler:handlers){// 检查处理器是否适用且还有金额需要支付if(handler.canProcess(userId)remaining0){// 执行支付PaymentResultresulthandler.processPayment(userId,remaining);// 记录支付结果paymentResults.put(handler.getClass().getSimpleName(),result);if(result.isSuccess()){// 支付成功减少剩余金额remaining-result.getPaidAmount();if(remaining0){break;// 金额已支付完成退出循环}}}}// 如果还有剩余金额未支付记录剩余金额if(remaining0){paymentResults.put(Remaining,newPaymentResult(false,remaining,String.format(支付未完成剩余金额: %.2f,remaining)));}returnpaymentResults;}}4、测试类/** * 组合支付系统演示类 */publicclassCompositePaymentSystem{publicstaticvoidmain(String[]args){// 初始化支付上下文CompositePaymentContextpaymentContextnewCompositePaymentContext();// 注册支付处理器顺序决定了支付尝试的优先级paymentContext.addHandler(newCorporatePaymentHandler());// 先尝试企业账户paymentContext.addHandler(newPersonalPaymentHandler());// 再尝试个人账户// 测试用例1企业用户只需企业账户支付System.out.println(\n 测试用例1 );System.out.println(用户ID: emp_789, 支付金额: 3000.00);MapString,PaymentResultresults1paymentContext.executePayment(emp_789,3000);results1.forEach((handlerName,result)-{System.out.printf([%s] %s (支付金额: %.2f)%n,handlerName,result.getMessage(),result.getPaidAmount());});// 测试用例2企业用户需要组合支付System.out.println(\n 测试用例2 );System.out.println(用户ID: emp_789, 支付金额: 6000.00);MapString,PaymentResultresults2paymentContext.executePayment(emp_789,6000);results2.forEach((handlerName,result)-{System.out.printf([%s] %s (支付金额: %.2f)%n,handlerName,result.getMessage(),result.getPaidAmount());});// 测试用例3个人用户只需个人账户支付System.out.println(\n 测试用例3 );System.out.println(用户ID: user123, 支付金额: 1500.00);MapString,PaymentResultresults3paymentContext.executePayment(user123,1500);results3.forEach((handlerName,result)-{System.out.printf([%s] %s (支付金额: %.2f)%n,handlerName,result.getMessage(),result.getPaidAmount());});}}4 规则引擎01 概念电商系统中经常会有营销活动比如支付订单时给与用户一定程度的优惠比如活动规则满 1000 减200 满 500 减 100 。这种情况一般都是通过 if-else 做分支判断处理还是非常简单的。但假如需要频繁的修改活动规则那么就需要频繁的修改代码非常不容易维护开发成本 上线成本。此时引入规则引擎有顺其自然了。规则引擎的优势如下降低开发成本业务人员独立配置业务规则开发人员无需理解以往需要业务人员告诉开发人员开发人员需要理解才能开发并且还需要大量的测试来确定是否正确而且开发结果还容易和提出的业务有偏差种种都导致开发成本上升增加业务的透明度业务人员配置之后其它人业务人员也能看到提高了规则改动的效率和上线的速度通过规则引擎我们只需要将业务人员配置的规则转换成一个规则字符串然后将该规则字符串保存进数据库中当使用该规则时只传递该规则所需要的参数便可以直接计算出结果。02 例子回到刚才营销活动 使用 AviatorScript 规则引擎的流程如下1、创建运营脚本将脚本存储在数据库 或者 配置中心。if(amount1000){return200;}elsif(amount500){return100;}else{return0;}2、定义调用方法 。publicstaticBigDecimalgetDiscount(BigDecimalamount,Stringrule){// 执行规则并计算最终价格MapString,ObjectenvnewHashMapString,Object();env.put(amount,amount);ExpressionexpressionAviatorEvaluator.compile(DigestUtils.md5Hex(rule.getBytes()),rule,true);Objectresultexpression.execute(env);if(result!null){returnnewBigDecimal(String.valueOf(result));}returnnull;}3、执行测试例子// step1 : 此处可以从配置中心 或者数据库中获取Stringruleif (amount1000){\n return 200;\n}elsif(amount500){\n return 100;\n}else{\n return 0;\n}\n;// step 2: 通过 AviatorScript 执行BigDecimaldiscountgetDiscount(newBigDecimal(600),rule);System.out.println(discount:discount);// 执行结果是 discount:1005 总结在实际开发中我们经常会遇到需要根据不同条件执行不同逻辑的场景。传统的if-else嵌套方式虽然直观但随着业务复杂度增加会导致代码臃肿、可读性差、维护困难等问题。本文总结了四种优雅的设计模式来解决这类条件爆炸问题。01. 策略模式灵活切换策略核心思想定义一系列算法将每个算法封装起来并使它们可以相互替换。适用场景系统中有多个相似的类仅在行为上有差异需要在运行时动态选择算法需要避免暴露复杂的条件判断优势符合开闭原则易于扩展新策略消除大量条件语句算法可以自由切换示例支付系统中不同支付方式的处理如信用卡、支付宝、微信支付等。02. SPI机制服务发现的优雅实现核心思想通过配置文件动态发现和加载服务实现。适用场景需要实现插件化架构服务提供方需要独立于服务调用方需要运行时动态发现服务优势实现完全解耦新增实现无需修改主代码支持热插拔示例JDBC驱动加载、Dubbo的扩展点实现。03. 责任链模式处理流程的链式传递核心思想将请求的发送者和接收者解耦使多个对象都有机会处理请求。适用场景有多个对象可以处理同一请求需要动态指定处理流程处理顺序很重要优势降低耦合度灵活调整处理顺序新增处理器方便示例支付流程中的多账户组合支付、审批流程等。04. 规则引擎业务规则的动态管理核心思想将业务规则从代码中分离实现动态配置。适用场景业务规则频繁变化需要非技术人员配置规则规则复杂度高优势业务人员可自主配置降低开发成本提高规则透明度示例营销活动规则、风控规则等。05 设计原则指导在实际选择解决方案时应遵循以下原则单一职责原则每个类/模块只负责一个功能开闭原则对扩展开放对修改关闭高内聚低耦合模块内部高度聚合模块间依赖最小化KISS原则保持简单直接不过度设计没有放之四海而皆准的解决方案需要根据具体业务场景选择最合适的方式而不是一味追求技术的新颖或复杂。最终目标是写出可维护、可扩展、高内聚低耦合的代码。