💪🏻 制定明确可量化的目标,并且坚持默默的做事。
引言
在编程开发的领域中,真正出类拔萃的,无疑是设计。运用深邃的架构思维、丰富的经验心得以及无穷的才华灵感,我们打造出无可挑剔的系统。真正的开发者会把他们编写的代码视作一件珍贵的作品来细心打磨和欣赏,而这份工作对他们而言,早已超越了普通的工作范畴,它蕴含着一种工匠精神的追求。 |
✨Hi设计爱好者们!你是否曾在软件设计的迷宫中迷失,寻找一种既灵活又高效的方法来处理复杂的请求?今天,就让我带你走进一个神奇的模式——责任链模式(Chain of Responsibility Pattern)。它就像是一根能量传递的链条,给你的项目增加了一个个能干的助手,让请求沿着链条
一、案例场景🔍
1.1 经典的运用场景
责任链模式的一些经典运用场景:
- ✨电商订单处理流程: 在电商系统中,订单处理是一个涉及多个环节、多个处理者的复杂流程。从用户下单到订单最终完成,中间可能包括验证订单信息、检查库存、计算价格、生成物流信息等一系列步骤。这些步骤往往不是由一个单一的模块或处理者来完成的,而是由一系列的处理者按顺序或条件逐个处理,形成一个责任链。
- ✨工作流引擎: 企业应用中的工作流引擎,可以配置一系列的业务规则和处理过程。每个工作流节点都可以视作责任链中的一个处理器,当一项任务在一个节点完成后,自动流转到下一个节点。
- ✨权限控制系统: 在很多系统中,根据不同用户的权限等级来授予对系统资源的访问。请求开始时会遇到最初级的权限检查,如果用户权限不符合要求,将请求上报到更高一级的权限检查,直至找到合适的权限等级,或者最终拒绝请求。
下面我们来实现电商订单处理流程。
1.2 一坨坨代码实现😻
在不使用设计模式的情况下,实现一个简化的电商订单处理流程可以通过一个简单的类和方法调用来完成。下面是一个简化的Java实现,它演示了如何按顺序处理订单验证、库存检查、价格计算和物流信息生成的步骤。
注:该实现是为了展示目的而简化的,并没有包含真实的数据库交互、错误处理或并发控制等复杂功能
public class Order {
private String userId;
private String productId;
private int quantity;
private double price;
private String status;
private String shippingInfo;
// 构造函数、getter和setter省略...
public boolean validateOrder() {
// 这里是简化的订单验证逻辑
if (userId == null || productId == null || quantity <= 0) {
status = "Invalid order";
return false;
}
status = "Order validated";
return true;
}
public boolean checkInventory() {
// 这里是简化的库存检查逻辑
if (quantity > 10) {
// 假设库存只有10个
status = "Out of stock";
return false;
}
status = "Inventory checked";
return true;
}
public void calculatePrice() {
// 这里是简化的价格计算逻辑
price = quantity * 100; // 假设每个产品100元
status = "Price calculated";
}
public void generateShippingInfo() {
// 这里是简化的物流信息生成逻辑
shippingInfo = "Shipping to user " + userId; // 假设物流信息就是用户ID
status = "Shipping info generated";
}
public void processOrder() {
if (!validateOrder()) {
System.out.println(status);
return;
}
if (!checkInventory()) {
System.out.println(status);
return;
}
calculatePrice();
System.out.println("Order total price: " + price);
generateShippingInfo();
System.out.println(shippingInfo);
System.out.println("Order processing completed successfully.");
}
public static void main(String[] args) {
Order order = new Order();
// 假设设置了一些订单信息...
order.processOrder();
}
}
在这个例子中,Order 类包含了处理订单所需的所有逻辑。processOrder 方法按照顺序调用其他方法来验证订单、检查库存、计算价格和生成物流信息。每个方法都更新订单的状态,并在需要时返回处理结果。
注:这个实现是线性的,并且没有使用责任链模式。每个方法都直接由Order类调用,并且没有将处理逻辑分散到独立的处理者对象中。这种方式虽然简单直接,但不够灵活,也不易于扩展或修改。如果需要在不同条件下改变处理流程或添加新的处理步骤,就需要修改Order类的代码,这违反了开闭原则。
1.3 痛点
上述实现是否违背了软件设计原则,我们可以逐一对照几个关键原则进行分析:
- 单一职责原则(Single Responsibility Principle, SRP):
上述实现中,Order 类承担了多个职责,包括订单验证、库存检查、价格计算和物流信息生成。这违背了单一职责原则,因为一个类应该只有一个引起变化的原因。在这个例子中,任何一个功能点的变更都可能需要修改Order类,增加了类的复杂性和维护成本。 - 开放封闭原则(Open/Closed Principle, OCP):
实现中的processOrder方法是封闭的,因为它直接调用了其他私有方法,并且这些方法的顺序和逻辑是硬编码的。如果需要添加新的订单处理步骤或修改现有步骤的顺序,就必须修改Order类的代码。这违背了开放封闭原则,因为软件实体应该对扩展开放,对修改封闭。 - 里氏替换原则(Liskov Substitution Principle, LSP):
虽然这个原则主要涉及子类和父类之间的关系,但在上述实现中没有明显的继承关系,因此这个原则在此上下文中不适用。然而,如果未来有子类继承自Order类,并且重写了某些方法,但没有按照基类的预期行为来实现,那么就可能违反里氏替换原则。 - 接口隔离原则(Interface Segregation Principle, ISP):
由于实现中没有定义接口,而是直接在Order类中实现了所有功能,因此无法评估是否违反了接口隔离原则。但是,如果将这些功能拆分成不同的接口,并让Order类实现它们,可能会提高代码的灵活性和可维护性。 - 依赖倒置原则(Dependency Inversion Principle, DIP):
在上述实现中,Order类依赖于具体的处理逻辑,而这些逻辑是硬编码在类中的。这导致了高层模块(Order类)依赖于低层模块(订单处理的各个步骤),违反了依赖倒置原则。根据DIP,高层模块不应该依赖于低层模块,它们都应该依赖于抽象。二、解决方案及模式讲解🤔
用来解决上述问题的一个合理的解决方案,就是使用责任链链模式。那么什么是责任链链模式呢?2.1 定义
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
2.2 案例分析
2.3 责任链模式的结构图及说明
主要组件:
- Handler(抽象处理者): 定义了一个接口或抽象类,用于处理请求。通常包含一个方法来处理请求(handleRequest()),以及通常还有一个成员变量来保存链中下一个Handler的引用。
- ConcreteHandler(具体处理者): 实现或扩展Handler接口或类,提供了请求的实际处理逻辑。如果当前对象无法处理请求,它会将请求传递给它的后继者(successor)。
- Client(客户端): 发起请求并使用责任链模式。客户端可能不知道链中的具体处理者,只是启动请求处理。
说明:
- Handler 是一个抽象类或者接口,包含需要实现的 handleRequest() 方法用于处理请求,以及 setSuccessor() 方法用于设置链中的下一个处理者。
- ConcreteHandler1 和 ConcreteHandler2 是 Handler 的具体实现。它们重写 handleRequest() 方法处理它们能处理的请求。如果它不能处理某个请求,它会调用父类的 handleRequest() 方法将请求传递给链上的下一个对象。
- 处理流向:请求从 ConcreteHandler1 开始,如果 A 可以处理,它就会停止传播;如果它不能处理,它会将请求传给 ConcreteHandlerB,这样的传递继续直到找到适当的处理者或者链条结束。
2.4 责任链模式的工作原理
让我们来形象地揭开责任链模式的层层神秘面纱。
2.4.1 分担责任,接力处理——责任链模式解析
责任链模式,其核心就在于创建一个对象链来处理特定的请求。与其说它是一条链,不如说它是一个由多个处理节点组成的处理接力赛道。🏃♀️🏃 每一个节点负责处理一部分工作,并决定是否将请求传递到链中的下一个节点。 |
在这个模式中,发送者不需要知道其请求最终由谁来处理,实现了发送者和接收者之间的解耦。这就像是你丢出一个问题球,而球会被几个人依次接住尝试解答,直至问题被成功解决为止。🤹♀️👩💼
2.4.2 何时传递火炬?——智能判断的必要
不难想象,责任链上的每个处理者如同接力跑中的运动员,他们需要明智地判断何时将请求传递给下一个队友。在编程中,这意味着每个对象需要有自己的处理逻辑,确定自己是否有能力处理这一请求,或者它应该将请求传递给链条中的下一环节。🔍📋 |
这样的结构既可以实现请求的快速处理,也保留了在链的不同部分灵活处理请求的能力。如同接力赛里跑得最快的不一定是第一个跑者,而是那个能在关键时刻冲刺的选手一样,责任链模式优化了整个请求处理流程,使得决策的灵活性和效率大大提高。
2.4.3 流转有序——优雅的请求流转
责任链模式不仅仅将复杂问题简化了,更赋予了代码整洁和可维护的特质。通过将请求在链上有序地流转,每个节点有机会展示其价值,同时也未雨绸缪为可能出现的新节点的加入做好了准备。💡💼 |
该模式让每个处理者都集中于自己的职责范围内,优化了处理逻辑,并且,在新的处理需求出现时,可以轻松地向链中添加新的处理节点,而不会对现有代码架构造成破坏,保证了系统的整体健壮性同时也提供了极大的扩展性。
2.5 使用责任链模式重构示例
为了实现责任链模式来处理订单流程,我们可以定义一个处理者接口,并为每个处理步骤创建一个具体的处理者类。这些处理者类将形成一条链,请求(即订单处理)沿着这条链传递,直到某个处理者处理它或请求被完全拒绝。
下面是一个使用责任链模式实现的订单处理系统的示例:
- 定义处理者接口
public interface OrderProcessor {
void setNext(OrderProcessor nextProcessor);
void processOrder(Order order);
}
- 具体的处理者类 - 订单验证
public class OrderValidator implements OrderProcessor {
private OrderProcessor nextProcessor;
@Override
public void setNext(OrderProcessor nextProcessor) {
this.nextProcessor = nextProcessor;
}
@Override
public void processOrder(Order order) {
if (order.getUserId() == null || order.getProductId() == null || order.getQuantity() <= 0) {
System.out.println("Invalid order. Processing terminated.");
return;
}
System.out.println("Order validated.");
if (nextProcessor != null) {
nextProcessor.processOrder(order);
}
}
}
- 具体的处理者类 - 库存检查
pubilc class InventoryChecker implements OrderProcessor {
private OrderProcessor nextProcessor;
@Override
public void setNext(OrderProcessor nextProcessor) {
this.nextProcessor = nextProcessor;
}
@Override
public void processOrder(Order order) {
if (order.getQuantity() > 10) {
// 假设库存只有10个
System.out.println("Out of stock. Processing terminated.");
return;
}
System.out.println("Inventory checked.");
if (nextProcessor != null) {
nextProcessor.processOrder(order);
}
}
}
- 具体的处理者类 - 价格计算
public class PriceCalculator implements OrderProcessor {
private OrderProcessor nextProcessor;
@Override
public void setNext(OrderProcessor nextProcessor) {
this.nextProcessor = nextProcessor;
}
@Override
public void processOrder(Order order) {
double price = order.getQuantity() * 100; // 假设每个产品100元
order.setPrice(price);
System.out.println("Price calculated: " + price);
if (nextProcessor != null) {
nextProcessor.processOrder(order);
}
}
}
- 具体的处理者类 - 物流信息生成
public class ShippingInfoGenerator implements OrderProcessor {
@Override
public void setNext(OrderProcessor nextProcessor) {
// 这个类是链中的最后一个,因此不需要设置下一个处理者
}
@Override
public void processOrder(Order order) {
String shippingInfo = "Shipping to user " + order.getUserId(); // 假设物流信息就是用户ID
order.setShippingInfo(shippingInfo);
System.out.println("Shipping info generated: " + shippingInfo);
System.out.println("Order processing completed successfully.");
}
}
- 订单类
public class Order {
private String userId;
private String productId;
private int quantity;
private double price;
private String shippingInfo;
// 构造函数、getter和setter
// ...
// 为了简化示例,这里省略了构造函数和其他getter/setter方法
}
- 客户端代码
public class Client {
public static void main(String[] args) {
// 创建订单处理链
OrderProcessor validator = new OrderValidator();
OrderProcessor inventoryChecker = new InventoryChecker();
OrderProcessor priceCalculator = new PriceCalculator();
OrderProcessor shippingInfoGenerator = new ShippingInfoGenerator();
validator.setNext(inventoryChecker);
inventoryChecker.setNext(priceCalculator);
priceCalculator.setNext(shippingInfoGenerator);
// 创建订单并处理
Order order = new Order();
// 假设设置了一些订单信息...
// order.setUserId(...);
// order.setProductId(...);
// order.setQuantity(...);
validator.processOrder(order);
}
}
在这个实现中,我们定义了OrderProcessor接口,并为每个处理步骤(验证、库存检查、价格计算和物流信息生成)创建了具体的处理者类。这些类实现了OrderProcessor接口,并通过setNext方法链接在一起,形成了责任链。
客户端代码(Client类)负责创建订单处理链,并将订单传递给链的第一个处理者(验证器)进行处理。请求沿着链传递,每个处理者根据自己的职责处理请求,然后可能将请求传递给链中的下一个处理者。
注:为了简化示例,省略了订单类的完整实现(包括构造函数和getter/setter方法)。此外,这个实现是同步的,并且没有考虑并发处理或错误处理。
2.6 重构后解决的问题
上述实现通过使用责任链模式解决了多个软件设计原则,具体如下:
- 单一职责原则(Single Responsibility Principle, SRP):
- 解决情况:每个处理者类(如OrderValidator、InventoryChecker等)只负责订单处理的一个特定方面(验证、库存检查等)。这使得代码更加清晰、模块化,并且每个类的职责明确。
- 理由:当需要修改订单处理的某个方面时,只需修改相应的处理者类,而不会影响其他不相关的功能。这提高了代码的可维护性和可读性。
- 开放封闭原则(Open/Closed Principle, OCP):
- 解决情况:责任链模式允许在不修改现有代码的情况下添加新的处理者类。例如,如果未来需要添加一个新的处理步骤(如税务计算),只需创建一个新的处理者类并将其插入到链中即可。
- 理由:这种设计使得系统对扩展开放(可以添加新的处理者类),同时对修改封闭(无需修改现有的处理者类或客户端代码)。这有助于保持系统的稳定性和可维护性。
- 里氏替换原则(Liskov Substitution Principle, LSP):
- 解决情况:在此实现中,虽然没有直接使用继承(因此里氏替换原则的直接应用不太明显),但每个处理者都遵循相同的接口约定(OrderProcessor接口)。这意味着任何实现了该接口的处理者都可以被替换为其他实现了相同接口的处理者,而不会破坏程序的正确性。
- 理由:遵循接口约定的处理者之间的替换性保证了系统的灵活性和可扩展性。如果未来需要替换某个处理者的实现,只需确保新实现符合接口约定即可。
- 接口隔离原则(Interface Segregation Principle, ISP):
- 解决情况:OrderProcessor接口被设计为小而专注的,只包含与订单处理直接相关的方法(setNext和processOrder)。这使得实现该接口的类只需要关注它们自己的职责,而不需要实现不相关的方法。
- 理由:通过将接口拆分为小而专注的部分,可以减少类之间的耦合度,提高代码的可读性和可维护性。同时,这也使得每个处理者类更加内聚和专注于自己的功能。
- 依赖倒置原则(Dependency Inversion Principle, DIP):
- 解决情况:在上述实现中,处理者类之间以及处理者与具体订单类之间的依赖关系是通过接口或抽象类来定义的(尽管示例中没有明确显示对抽象类的使用)。这意味着高层模块(如处理者类)不依赖于低层模块的具体实现,而是依赖于抽象(接口或抽象类)。
- 理由:依赖倒置原则有助于减少类之间的直接依赖,从而提高代码的可测试性和可维护性。通过依赖于抽象而不是具体实现,可以更容易地替换或修改低层模块的实现,而不会对高层模块产生重大影响。这在大型系统中尤为重要,因为它允许不同部分的代码以更松散的方式协同工作。
通过使用责任链模式,上述实现成功地解决了多个重要的软件设计原则,从而提高了代码的质量、可维护性和可扩展性。
2.7 重构后代码结构的具体影响
责任链模式对代码结构的影响主要体现在以下几个方面:
- 解耦:通过将请求的处理逻辑分散到多个独立的处理者中,责任链模式降低了代码之间的耦合度。这使得每个处理者都可以独立地进行修改和测试,而不会影响到其他处理者。
- 灵活性:责任链模式允许你动态地改变处理者的顺序或添加新的处理者,而无需修改现有的代码。这增加了代码的灵活性和可扩展性。
- 简化客户端代码:客户端只需要知道如何将请求发送给责任链上的某个处理者,而不需要了解具体的处理逻辑和链的结构。这简化了客户端代码并提高了其可维护性。
- 明确的职责划分:每个处理者都有明确的职责范围,这有助于提高代码的可读性和可理解性。同时,这也使得每个处理者都可以专注于自己的职责,降低了代码的复杂性。
2.8 重构后的改进效果
在实际运行中,责任链模式可以带来以下改进效果: - 提高代码质量:通过解耦和职责划分,责任链模式有助于降低代码的复杂性并提高代码的可读性和可维护性。同时,它还可以帮助你发现和解决潜在的设计问题,从而提高代码的质量。
- 增强可维护性:由于每个处理者都是独立的且职责明确,因此当需要修改或扩展某个处理者的功能时,你只需要修改相应的代码而不会影响到其他处理者。这大大增强了代码的可维护性。
- 提高可扩展性:责任链模式允许你动态地添加新的处理者或改变处理者的顺序,这使得系统能够轻松地适应新的业务需求或变化。同时,由于每个处理者都是独立的,因此你可以根据需要选择性地替换或升级某个处理者,而不会对整个系统造成太大的影响。
- 优化性能:在某些情况下,通过合理地设计责任链和处理者的职责范围,你可以优化系统的性能。例如,你可以将计算密集型的操作放在链的末端,以避免在不需要的情况下执行这些操作。或者你可以使用异步处理或并行处理来加快请求的处理速度。当然,这些优化措施需要根据具体的业务需求和系统环境来制定和实施。
综上所述,责任链模式在电商订单处理流程中的应用可以显著提升代码质量、可维护性和可扩展性。通过合理地设计和使用责任链模式,你可以构建一个灵活、高效且易于维护的电商订单处理系统。
三、总结 👨🏫
3.1 优势
- 解耦请求发送者和接收者:责任链模式允许请求的发送者不知道具体由哪个接收者处理请求,从而降低了发送者和接收者之间的耦合度。这有助于代码的灵活性和可维护性。
- 处理者顺序灵活:通过动态地改变链内的成员或者调整它们的顺序,可以灵活地应对不同的业务场景。这种灵活性使得责任链模式在处理复杂业务逻辑时具有很大的优势。
- 易于扩展:当需要添加新的处理者时,只需在链上添加新的节点即可,而不需要修改现有的代码。这有助于减少代码的改动量,降低维护成本。
- 提高代码可读性:通过将不同的处理逻辑封装在各自的处理者中,可以使得代码结构更加清晰,易于理解和维护。每个处理者只关注自己的处理逻辑,降低了代码的复杂性。
3.2 缺点
- 可能导致请求得不到处理:如果没有任何处理者能够处理请求,或者链上的处理者都没有正确地传递请求,那么请求可能会得不到处理。为了避免这种情况,通常需要设置一个默认的处理者或者确保链上的至少有一个处理者能够处理所有请求。
- 性能影响:由于请求需要在链上依次传递,因此可能会产生一定的性能开销。特别是在链较长或者处理者处理逻辑较复杂的情况下,性能问题可能会更加明显。
- 调试困难:当请求在链上传递时,如果某个处理者出现了问题,可能会导致整个链的处理失败。这种情况下,调试可能会比较困难,因为需要跟踪请求的传递过程并找到出问题的处理者。
- 可能引入循环引用:在实现责任链模式时,如果不小心引入了循环引用(即链上的某个处理者引用了链上的其他处理者,而这些处理者又间接地引用了该处理者),可能会导致内存泄漏或者栈溢出等问题。
3.3 挑战和限制
- 确定处理者的顺序:在某些场景下,处理者的顺序可能对结果产生重要影响。因此,需要仔细考虑如何确定处理者的顺序,以确保请求得到正确处理。
- 处理者的职责划分:为了避免处理者之间的职责重叠或者模糊不清的职责划分,需要明确每个处理者的职责范围和处理逻辑。这有助于确保请求得到正确处理,并提高代码的可维护性。
- 错误处理和异常传播:当链上的某个处理者无法处理请求或者处理失败时,需要考虑如何传播错误或者异常信息。这有助于及时发现问题并进行相应的处理。
- 性能优化:为了提高性能,可以考虑对链进行优化,如减少链的长度、合并相邻的处理者等。同时,也可以考虑使用异步处理或者并行处理等技术来提高处理效率。