概要
程序中经常出现这样的逻辑,收到XX请求,进行XX相关的响应处理,收到YY请求,则进行YY的响应处理。请求与响应之间彼此配对,所以代码也往往会为这种配对提供一对一的对应关系。比如说之前说过的Command模式中,一种Command会跟一个Performer对应起来确保这种逻辑关系。那如果某个请求该如何响应是未知或动态决定的,如何处理呢?答案之一,用一堆条件来判断限制啊?很多情况下,没错,应该这样做。但有时这样做会不会太累啊?有没有什么其他方案呢?Chain of Responsibility模式就是选择之一,通过对所有的响应方建立职责链,请求方不需要知道由哪个响应来执行,只需要把请求丢给职责链,职责链的每个元素都有可以执行,可以传递,当然也可以中止传递。
目的
避免请求方耦合于执行方,请求方可以不需要知道哪个目标会执行该请求,而只需要把请求传递给职责链就ok。执行方被动态串联形成职责链,执行条件由每个元素自己控制。
实例
想到这样一个伪例子,当收到的命令小于Command1或者等于Command3时,执行Performer1,当命令大于Command1,小于Command5并且不等于Command3时,执行Performer2,当命令等于Command7时,也执行Performer2,当命令大于Command5,小于Command9并且不等于Command7时,执行Performer3。好纠结的条件啊,其实代码倒还好,看下实现:
void handle_request(int cmd) {
if (cmd <= COMMAND_1) {
perform1.action();
} else if (cmd <= COMMAND_5) {
if (cmd == COMMAND_3) {
perform1.action();
} else {
perform2.action();
}
} else if (cmd <= COMMAND_9) {
if (cmd == COMMAND_7) {
perform2.action();
} else {
perform3.action();
}
} else {
}
}
确实处理逻辑还不算太负责,但是万一又来n个条件逻辑呢,所以代码只会变得越来越复杂,perform的触发方会很纠结与这种代码的维护与测试。那用Chain of Responsibility模式呢?怎么实现?先类图设计:
太抽象了,还是直接看下代码怎么实现的,首先Handler类提供handle_request接口,并提供set_successor方法可以设置职责链上下一个Handler是谁,每个具体的Handler子类实现自己的handle_request,其中关注某个performer运行需要的条件就可以了,当然也别忘了在不符合条件时把cmd传给下一个Handler。
class Handler {
public:
virtual void handle_request(int cmd) = 0;
public void set_successor(Handler* successor)
{
m_successor = successor;
}
protected:
Handler* m_successor;
};
class ConcreteHandler1 : public Handler {
public:
void handle_request(int cmd) {
if (cmd <= COMMAND_1 || cmd == COMMAND_3) {
performer1.action();
} else if (m_successor != NULL) {
m_successor->handle_request(cmd);
}
}
};
class ConcreteHandler2 : public Handler {
public:
void handle_request(int cmd) {
if (cmd <= COMMAND_5 || cmd == COMMAND_7) {
performer2.action();
} else if (m_successor != NULL) {
m_successor->handle_request(cmd);
}
}
};
class ConcreteHandler3 : public Handler {
public:
void handle_request(int cmd) {
if (cmd <= COMMAND_9) {
performer3.action();
} else if (m_successor != NULL) {
m_successor->handle_request(cmd);
}
}
};
具体使用可以如下,都可以通过h1来处理request,最后cmd都会由对应的Handler进行处理:
Handler* h1 = new ConcreteHandler1();
Handler* h2 = new ConcreteHandler2();
Handler* h3 = new ConcreteHandler3();
h1->setSuccessor(h2);
h2->setSuccessor(h3);
h1->handle_request(COMMAND_1);
h1->handle_request(COMMAND_3);
h1->handle_request(COMMAND_5);
h1->handle_request(COMMAND_7);
h1->handle_request(COMMAND_9);
可能有人会发现,每个handle_request都需要去关心通过m_successor来转发有点麻烦,万一哪个地方忘了处理,那职责链就断了,让我们看看是否可以想点办法来改善这一点,看下如下代码是不是让实现更简单更健壮了:
class Handler {
public:
void handle_request(int cmd) {
if (handle_request_impl(cmd) == false && m_successor != NULL) {
m_successor->handle_request(cmd);
}
}
virtual bool handle_request_impl(int cmd) = 0;
public void set_successor(Handler* successor)
{
m_successor = successor;
}
private:
Handler* m_successor;
};
class ConcreteHandler1 : public Handler {
public:
bool handle_request_impl(int cmd) {
if (cmd <= COMMAND_1 || cmd == COMMAND_3) {
perform1.action();
return true ;
}
return false ;
}
};
class ConcreteHandler2 : public Handler {
public:
bool handle_request_impl(int cmd) {
if (cmd <= COMMAND_5 || cmd == COMMAND_7) {
perform2.action();
return true ;
}
return false ;
}
};
class ConcreteHandler3 : public Handler {
public:
bool handle_request_impl(int cmd) {
if (cmd <= COMMAND_9) {
perform3.action();
return true ;
}
return false ;
}
};
应用
除了以上示例中的例子,实际应用中有时还希望一个请求可能被多个Handler处理的情况,针对这种情况其实只要稍微修改下代码,控制handle_request_impl的返回值就可以做到。另外,如果不需要由调用方来调用set_successor组成职责链,那么甚至可以在每创建一个Handler时自动进入职责链,从而把set_successor的细节也隐藏掉,如何实现的话,自己尝试吧。