动机
- 在软件构建过程中,一个请求可能被多个对象处理。但是每个请求在运行时只能有一个接受者。如果显示指定将必不可少的带来请求发送者与接受者的紧耦合。
- 那么如何使请求的发送者不需要指定具体的接受者?让请求的接受者自己的在运行时决定来处理请求,从而使两者解耦合。
- 在许多情况下,多个对象可以处理某个请求,但具体由哪个对象来处理则取决于运行时的状态。职责链模式的动机是解耦请求的发送者和接受者,使多个对象都有机会处理这个请求。请求沿着一个由处理对象构成的链传递,直到有一个对象处理它为止。这种模式的好处是可以动态地改变处理链,并且可以通过增加或减少处理对象来灵活地应对变化。
定义与结构
定义
职责链模式(Chain of Responsibility Pattern)是一种行为设计模式,允许多个对象有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有对象处理它为止。
结构
这张结构图展示了设计模式中的职责链模式(Chain of Responsibility Pattern),但描述中有一处小误会,即Client类并不直接继承自Handler类,而是Client类依赖于一个Handler类型的对象。这里我们详细解释一下这个模式的结构和工作原理。
职责链模式
职责链模式是一种行为设计模式,它允许你将请求的发送者和接收者解耦,通过定义一个处理请求的接口来创建一系列的处理者,然后将它们连接成一条链。当接收到一个请求时,这个请求会沿着链传递,直到链中的某个处理者能够处理这个请求为止。
图中元素解释
-
Client:客户端角色,它创建了一个处理者链,并向链的第一个处理者对象提交请求。客户端通常不会自己处理请求,也不会知道链的结构。
-
Handler(处理者):这是一个抽象类或接口,声明了处理请求的接口,即HandleRequest()方法。它通常包含一个指向另一个处理者的引用(如后继处理者
successor
),如果这个处理者不能处理该请求,它会把请求转发给后继者。 -
ConcreteHandler1 和 ConcreteHandler2:具体处理者角色,实现了抽象处理者(Handler)中所声明的处理请求的方法。具体处理者接收到请求后,可以处理它,也可以将请求传递给链中的下一个具体处理者。在图中,ConcreteHandler2是ConcreteHandler1的子类,这意味着它继承了ConcreteHandler1的所有行为,并且可以进一步特化或覆盖某些行为。
-
HandleRequest():处理请求的方法,它定义在Handler接口中,并由ConcreteHandler1和ConcreteHandler2具体实现。这个方法通常包含一些逻辑,用于判断这个处理者是否能够处理该请求。如果不能处理,则调用后继处理者的HandleRequest()方法。
-
successor:这个属性表示在处理者链中当前处理者的后继者。它用于在无法处理请求时将请求传递给链中的下一个处理者。
工作流程
- 客户端创建一个处理者链,并设置它们的后继者。
- 客户端向链的第一个处理者发送请求。
- 处理者检查自己是否能够处理该请求。
- 如果可以,它就处理它。
- 如果不可以,它就将请求转发给它的后继者,由后继者继续处理。
- 这个过程会一直持续下去,直到找到能够处理请求的处理者,或者处理者链结束(即没有后继者)。
这种设计模式的好处包括增加新的处理者变得更加容易,只需要将其加入到链中即可;同时,请求的发送者和接收者解耦,提高了系统的灵活性和可扩展性。
C++ 代码推导
以下是使用职责链模式的一个示例,在这个例子中,我们创建了一个责任链来处理客户请求。每个具体处理者决定是否处理请求,如果不能处理则将请求传递给下一个处理者。
#include <iostream>
#include <memory>
// 抽象处理者
class Handler {
public:
virtual ~Handler() = default;
void setNext(std::shared_ptr<Handler> next) {
this->nextHandler = next;
}
void handleRequest(int request) {
if (this->canHandle(request)) {
this->handle(request);
} else if (nextHandler) {
nextHandler->handleRequest(request);
} else {
std::cout << "No handler available for request " << request << std::endl;
}
}
protected:
virtual bool canHandle(int request) = 0;
virtual void handle(int request) = 0;
private:
std::shared_ptr<Handler> nextHandler = nullptr;
};
// 具体处理者A
class ConcreteHandlerA : public Handler {
protected:
bool canHandle(int request) override {
return request < 10;
}
void handle(int request) override {
std::cout << "ConcreteHandlerA handled request " << request << std::endl;
}
};
// 具体处理者B
class ConcreteHandlerB : public Handler {
protected:
bool canHandle(int request) override {
return request >= 10 && request < 20;
}
void handle(int request) override {
std::cout << "ConcreteHandlerB handled request " << request << std::endl;
}
};
// 具体处理者C
class ConcreteHandlerC : public Handler {
protected:
bool canHandle(int request) override {
return request >= 20;
}
void handle(int request) override {
std::cout << "ConcreteHandlerC handled request " << request << std::endl;
}
};
int main() {
// 创建处理者对象
auto handlerA = std::make_shared<ConcreteHandlerA>();
auto handlerB = std::make_shared<ConcreteHandlerB>();
auto handlerC = std::make_shared<ConcreteHandlerC>();
// 设置责任链
handlerA->setNext(handlerB);
handlerB->setNext(handlerC);
// 客户端发出请求
int requests[] = {5, 15, 25, 30};
for (int request : requests) {
handlerA->handleRequest(request);
}
return 0;
}
优缺点
优点:
- 降低耦合度:请求的发送者和接收者解耦,发送者无需了解处理者的具体信息。
- 责任链灵活:通过改变链内的成员或调整链的顺序,允许动态地增加或删除责任链上的处理者。
- 分离关注点:每个处理者只关注自己能处理的请求,其他请求会传递给下一个处理者。
缺点:
- 不保证被处理:如果没有处理者处理该请求,则请求可能会丢失。
- 性能开销:如果链条太长,可能导致请求的处理速度变慢,影响系统性能。
- 调试困难:由于请求会沿着链条传递,调试时可能需要逐步跟踪请求通过链条的每一步。
应用场景
职责链模式常用于以下场景:
- 多个对象可以处理同一请求,但具体由哪个对象处理由运行时决定。
- 需要动态地指定处理者。例如日志处理系统中,可以将不同级别的日志处理器串联起来,根据日志的级别来决定处理的处理器。
- 需要在处理者间解耦,例如事件分发系统、审批流程等。
职责链模式在许多系统中都是一种非常有用的设计模式,特别是在需要灵活配置请求处理逻辑的场景下。
总结
- 责任链模式对应用场合在于一个请求可能有多个接受者。但是最后真正的接受者只有一个,这时候请求发送者与接受者的耦合有可能出现变化脆弱的症状。这次练的目的就是将二者进行解耦。从而更好地应对变化。
- 应用了责任链模式后,对象的职责分配将更加灵活我们可以在运行时动态添加或者修改请求的处理职责。
- 如果请求传递到职责链的末尾仍得不到处理,应该有一个合理的缺省机制。这也是每一个接受对象的责任,而不是发出请求的对象的责任。