职责链模式是一种行为设计模式,其核心思想是将请求的发送者与接收者解耦,使多个对象都有机会处理这个请求。为了实现这一点,这些对象形成了一个链,并且请求在链上逐个传递,直到某个对象处理它为止。
现实中的职责链模式
职责链模式的例子在现实中并不难找到,以下就是两个常见的跟职责链模式相关的场景。
-
客户服务与支持:
当你拨打一个客户服务热线时,首先可能会有一个自动的语音响应系统提供一些基本选项。如果你的问题没有得到解决,你可能会被转接到一个客服代表。如果该代表无法帮助你,他们可能会将你转接到一个更高级别或专业领域的代表。
-
踢皮球
这个问题请找相关部门解决,这里解决不了。让你去找到能解决改该问题的相关部门,然鹅你永远找不到。
实际开发中的职责链模式
职责链模式在实际开发中主要用于将请求的发送者和接收者解耦,让多个对象都有机会处理请求。
先来看一个没有使用职责链模式的例子:假设你有一个订单处理系统,其中有三个步骤:库存检查、付款处理和发货处理。
请看下面的代码:
js
class OrderProcessor {
process(order) {
if (this.checkInventory(order)) {
console.log("库存足够");
if (this.processPayment(order.paymentDetails)) {
console.log("付款成功");
this.shipOrder();
} else {
console.log("付款失败");
}
} else {
console.log("库存不足");
}
}
checkInventory(order) {
// 检查库存的逻辑
return true;
}
processPayment(paymentDetails) {
// 处理付款的逻辑
return true;
}
shipOrder() {
// 发货的逻辑
}
}
上面的代码将所有处理逻辑放在一个类中。如果我们要修改任何处理步骤,都需要修改 OrderProcessor 这个类,这违反了开放封闭原则。
接下来,我们使用职责链模式重新设计:
js
class Handler {
setNext(handler) {
this.next = handler;
return handler;
}
handle(order) {}
}
class InventoryCheck extends Handler {
handle(order) {
if (true /*检查库存逻辑*/) {
console.log("库存足够");
if (this.next) {
this.next.handle(order);
}
} else {
console.log("库存不足");
}
}
}
class PaymentProcessor extends Handler {
handle(order) {
if (true /*付款处理逻辑*/) {
console.log("付款成功");
if (this.next) {
this.next.handle(order);
}
} else {
console.log("付款失败");
}
}
}
class Shipper extends Handler {
handle(order) {
console.log("发货");
}
}
const orderHandler = new InventoryCheck();
orderHandler.setNext(new PaymentProcessor()).setNext(new Shipper());
orderHandler.handle({
/*订单详情*/
});
在使用了职责链模式后:每个处理步骤都有自己的类,职责更加明确。并且添加新的处理步骤或修改现有步骤都变得更加容易,因为每个处理器都遵循同一套接口。我们可以灵活地组合或重新排序处理器。这使得代码更加模块化和可扩展。
异步的职责链
在现实开发中,我们会经常遇到一些异步的问题,比如我们要在节点函数中发起一些异步操作,如网络请求、文件读写、定时任务等。
假设我们需要完成三个异步任务:异步获取用户信息、异步获取用户订单、异步获取订单详情。
首先我们来看看不适用职责链模式的情况下怎么样的,如下代码所示:
js
function getUser() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: "Alice" });
}, 1000);
});
}
function getUserOrders(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ orderId: 101, userId }]);
}, 1000);
});
}
function getOrderDetails(orderId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ orderId, product: "Book" });
}, 1000);
});
}
getUser()
.then((user) => {
return getUserOrders(user.id);
})
.then((orders) => {
return getOrderDetails(orders[0].orderId);
})
.then((orderDetails) => {
console.log(orderDetails);
});
这段代码采用了连续的 Promise 链,导致以下几个问题:
-
深度嵌套: 代码的结构呈现了"金字塔"的形状,随着业务逻辑的复杂度增加,代码会变得更加嵌套,不易于阅读。
-
错误处理不明确: 当中间某个 Promise 出错时,确定具体是哪一步出的错会比较困难。
-
不利于扩展: 如果需要增加、删除或更改某一步的逻辑,可能需要重构整个 Promise 链。
-
过度依赖顺序: 因为每一步的结果都依赖于前一步,导致代码的执行完全是线性的,无法利用并行执行提高效率。
接下来我们看看使用了职责链模式的情况是怎么样的,如下代码所示:
js
class AsyncHandler {
setNext(handler) {
this.next = handler;
return handler;
}
handle(request) {
return Promise.resolve(request);
}
}
class UserHandler extends AsyncHandler {
handle(request) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: "Alice" });
}, 1000);
}).then((user) => {
if (this.next) {
return this.next.handle(user);
}
return user;
});
}
}
class OrderHandler extends AsyncHandler {
handle(user) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ orderId: 101, userId: user.id }]);
}, 1000);
}).then((orders) => {
if (this.next) {
return this.next.handle(orders[0]);
}
return orders;
});
}
}
class OrderDetailsHandler extends AsyncHandler {
handle(order) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ orderId: order.orderId, product: "Book" });
}, 1000);
});
}
}
const handler = new UserHandler();
handler.setNext(new OrderHandler()).setNext(new OrderDetailsHandler());
handler.handle().then((orderDetails) => {
console.log(orderDetails);
});
通过使用异步职责链,我们能够更清晰地组织代码,每个处理器只关心自己的任务。同时,它也提供了一个统一的接口,使得添加、移除或更改处理器变得简单。
职责链模式在 react 中的应用
在 React 中,高阶组件是接受一个组件并返回一个新组件的函数。高阶组件是重用组件逻辑的一种方式。当结合职责链模式时,我们可以利用 HOC 创建一个链条,使得每个 HOC 执行特定的逻辑和校验。
假设我们有以下需求:
-
检查用户是否已登录。
-
检查用户是否有权限查看页面
让我们通过一个简单的示例来了解如何在高阶组件中使用职责链模式,首先,我们定义两个高阶组件来满足这些需求:
js
// 检查用户是否已登录
function withAuthentication(WrappedComponent) {
return function (props) {
if (!props.isLoggedIn) {
return <div>Please log in.</div>;
}
return <WrappedComponent {...props} />;
};
}
// 检查用户是否有权限
function withAuthorization(WrappedComponent) {
return function (props) {
if (!props.hasPermission) {
return <div>You do not have permission.</div>;
}
return <WrappedComponent {...props} />;
};
}
现在,我们可以组合这些 HOC,形成一个职责链,如下代码所示:
js
function MyComponent(props) {
return <div>你好 叼毛!</div>;
}
const EnhancedComponent = withAuthentication(withAuthorization(MyComponent));
当我们渲染 EnhancedComponent 时:
js
<EnhancedComponent isLoggedIn={true} hasPermission={true} />
withAuthentication HOC 首先检查 isLoggedIn。如果用户未登录,它会渲染一个提示用户登录的消息。如果用户已登录,控制权传递给下一个 HOC。
withAuthorization HOC 接下来检查 hasPermission。如果用户没有权限,它会渲染一个提示用户没有权限的消息。如果用户有权限,它会渲染原始的 MyComponent 组件。
在上面的代码中,我们将多个单独的校验步骤(在这里是检查登录和权限)组合成一个连续的校验链,其中每个 HOC 都是链上的一个节点。当任何校验失败时,链条断裂并渲染相应的消息。
职责链的优缺点
职责链模式是一种对象的行为模式,在其中对象接收并沿链传递请求,直到一个对象处理它为止。
这种设计模式,发送者不知道链中的哪一个对象处理了请求,处理者也不需要知道链的结构。这增加了两者之间的解耦。可以动态地添加或修改处理一个请求的结构。可以根据需要改变链内的成员或调整它们的顺序。
如果运用得当,职责链模式可以很好地帮助我们组织代码,但这种模式也并非没有弊端,首先我们不能保证某个请求一定会被链中的节点处理。例如上面踢皮球的例子,也许没有任何一个部门知道能跟解决,此时的请求就得不到答复,而是径直从链尾离开,或者抛 出一个错误异常。在这种情况下,我们可以在链尾增加一个保底的接受者节点来处理这种即将离开链尾的请求。
另外,职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避免过长的职责链带来的性能损耗。
参考文献
- 书籍:JavaScript 设计模式与开发实践
总结
职责链模式允许多个对象来处理单个请求,并将这些对象连接成链。一个请求在链上经过,直到一个对象处理它或整个链都不处理。这种模式有助于减少请求的发送者和接收者之间的耦合,但可能导致未处理的请求或性能问题。
职责链模式是许多编程概念的核心,如作用域链、原型链和 DOM 事件冒泡。结合组合模式,它能优化部件间的连接,提升整体效率。掌握此模式对编写高效代码至关重要。
最后分享两个我的两个开源项目,它们分别是:
这两个项目都会一直维护的,如果你也喜欢,欢迎 star 🚗🚗🚗