- 责任链模式
- 应用场景
- 责任链模式的参与者
- 模式案例
- 工单责任链
- 类关系图
- 详细编码设计
- 测试应用
- Jdk中的责任链模式
责任链模式
责任链模式是行为设计模式中的一种,责任链模式的主要目的是为了避免编程中请求的发送者与接收者耦合;通过将多个接收者对象链接在一起,从而为多个对象提供处理请求的机会,请求会沿着链往下传递,直到请求被合适的接收者处理。
在责任链模式中仅需要对请求发送者提供一个消息入口,而无需关心哪个接收者如何处理请求。通常我们会针对责任链赋予接收者一种能力注入的策略,允许责任链的能力收缩与扩张。
应用场景
编程中当多个处理者对象可以处理某种请求并且处理对象不必是特定处理者对象时,建议开发者应用责任链模式设计代码。
比如,我们的操作系统的事件处理机制,鼠标、键盘等可以生成事件,这些事件可以被多个处理程序处理,我们可以将这些事件处理程序用责任链模式链接,当事件发生时,责任链总能够正确处理当前的事件请求。
再例如:客户服务中心是很多企业都设定的部门,专注于为客户解决各种各样的问题,提供售前、售后等问题。假设以客户服务中心为例,我们的客户可以在终端设备上提交各类需要企业协助的问题,比如我之前在电商公司中,客户服务系统每天会接收到各类问题,但是这些问题会根据不同的分类被分派至不同部分或者不同指责权限的同事工单中,这便是一个很好应用责任链模式的应用场景。
我们假设问题分为售前商品咨询、优惠折扣、商品供应链、商品质量和售后退换服务五大类,这五大类问题分别由客服部门的不同子部门来为客户服务,各个小组都有自己的内部技能培训和解决问题的规章制度,无法跨部门提供服务。
那么当一个用户提交了一个指定咨询类别的问题之后,会有一个请求到达我们的工单中心,我们需要及时地分配给责任同学去处理,以便提供优质的客户服务。接下来我们以客户服务为案例来设计责任链模式的方案落地。
责任链模式的参与者
处理程序:可以是主要接收请求并将请求分派给处理程序链的接口,它只引用了责任链中的第一个处理程序,并且对其余处理程序一无所知;
具体处理程序:责任链以某种策略编排,链上具体处理请求的处理者,可以理解为事件消费者;
客户端:请求的发起端,在我们的案例中可以理解为客户工单提交者。
模式案例
设计一个客服服务中心,包括各类问题处理程序,假设我们按照问题分类建设一个问题处理责任链。对于一个问题会经过责任链流动,直到被处理或者被责任链最后一个处理者拒绝。
工单责任链
我们按照问题的发生时间来设计责任链:售前商品咨询 => 优惠折扣 => 商品供应链 => 商品质量 => 售后退换服务。
image.png类关系图
详细编码设计
首先设计我们的实体类:Service、Solution、Category、ServiceExchange.
Service.java
package com.iblog.pattern.responsibilityChain;
public class Service {
private final Category category;
private final String id;
private final String description;
private Service(Builder builder) {
this.category = builder.category;
this.id = builder.id;
this.description = builder.description;
}
public static class Builder {
public final Category category;
public String id;
public String description;
public Service build() {
return new Service(this);
}
public Builder(Category category) {
this.category = category;
}
public Builder setId(String id) {
this.id = id;
return this;
}
public Builder setDescription(String description) {
this.description = description;
return this;
}
}
public Category getCategory() {
return category;
}
public String getId() {
return id;
}
public String getDescription() {
return description;
}
}
Solution.java
package com.iblog.pattern.responsibilityChain;
public class Solution {
private final String id;
private final String serviceId;
private final boolean solved;
private final String solvedBy;
private Solution(Builder builder) {
this.id = builder.id;
this.serviceId = builder.serviceId;
this.solved = builder.solved;
this.solvedBy = builder.solvedBy;
}
public static class Builder {
public String id;
public final String serviceId;
public boolean solved;
public String solvedBy;
public Solution build() {
return new Solution(this);
}
public Builder(String serviceId) {
this.serviceId = serviceId;
}
public Builder setId(String id) {
this.id = id;
return this;
}
public Builder setSolved(boolean solved) {
this.solved = solved;
return this;
}
public Builder setSulvedBy(String solvedBy) {
this.solvedBy = solvedBy;
return this;
}
}
public String getId() {
return id;
}
public String getServiceId() {
return serviceId;
}
public boolean isSolved() {
return solved;
}
public String getSolvedBy() {
return solvedBy;
}
}
Category.java
package com.iblog.pattern.responsibilityChain;
public enum Category {
PRE_SALES_PRODUCT_CONSULTATION,
PROMOTIONS,
COMMODITY_SUPPLY_CHAIN,
PRODUCT_QUALITY,
RETURN_SERVICE,
OTHER
}
ServiceExchange.java
package com.iblog.pattern.responsibilityChain;
/**
* Wrapper the builder of service and solution in this class.
*/
public class ServiceExchange {
private final Service service;
private final Solution.Builder solution;
public ServiceExchange(Service service, Solution.Builder solution) {
this.service = service;
this.solution = solution;
}
public Service getService() {
return service;
}
public Solution.Builder getSolution() {
return solution;
}
}
面向接口编程实现处理程序,接口ServiceHandler.java:
package com.iblog.pattern.responsibilityChain.handler;
import com.iblog.pattern.responsibilityChain.ServiceExchange;
/**
* The interface that handle ServiceExchange
*/
public interface ServiceHandler {
void handle(ServiceExchange exchange);
}
具体处理程序:
chainServiceSupport.java
package com.iblog.pattern.responsibilityChain.handler;
import com.iblog.pattern.responsibilityChain.ServiceExchange;
/**
* The support endpoint.
*/
public class ServiceSupport implements ServiceHandler {
private final ServiceHandler handler;
public ServiceSupport(ServiceHandler handler) {
this.handler = handler;
}
@Override
public void handle(ServiceExchange exchange) {
this.handler.handle(exchange);
}
}
PreSalesSupport.java
package com.iblog.pattern.responsibilityChain.handler;
import com.iblog.pattern.responsibilityChain.Category;
import com.iblog.pattern.responsibilityChain.ServiceExchange;
public class PreSalesSupport implements ServiceHandler {
private final ServiceHandler next;
public PreSalesSupport(ServiceHandler next) {
this.next = next;;
}
@Override
public void handle(ServiceExchange exchange) {
if (exchange.getService().getCategory() == Category.PRE_SALES_PRODUCT_CONSULTATION) {
exchange.getSolution().setSolved(true);
exchange.getSolution().setSulvedBy(this.getClass().getSimpleName());
return;
}
if (next != null) {
next.handle(exchange);
} else {
throw new IllegalArgumentException("No handler found for " + exchange.getService().getId());
}
}
}
PromotionsSupport.java
package com.iblog.pattern.responsibilityChain.handler;
import com.iblog.pattern.responsibilityChain.Category;
import com.iblog.pattern.responsibilityChain.ServiceExchange;
public class PromotionsSupport implements ServiceHandler {
private final ServiceHandler next;
public PromotionsSupport(ServiceHandler next) {
this.next = next;
}
@Override
public void handle(ServiceExchange exchange) {
if (exchange.getService().getCategory() == Category.PROMOTIONS) {
exchange.getSolution().setSolved(true);
exchange.getSolution().setSulvedBy(this.getClass().getSimpleName());
return;
}
if (next != null) {
next.handle(exchange);
} else {
throw new IllegalArgumentException("No handler found for " + exchange.getService().getId());
}
}
}
CommoditySupplySupport.java
package com.iblog.pattern.responsibilityChain.handler;
import com.iblog.pattern.responsibilityChain.Category;
import com.iblog.pattern.responsibilityChain.ServiceExchange;
public class CommoditySupplySupport implements ServiceHandler {
private final ServiceHandler next;
public CommoditySupplySupport(ServiceHandler next) {
this.next = next;
}
@Override
public void handle(ServiceExchange exchange) {
if (exchange.getService().getCategory() == Category.COMMODITY_SUPPLY_CHAIN) {
exchange.getSolution().setSolved(true);
exchange.getSolution().setSulvedBy(this.getClass().getSimpleName());
return;
}
if (next != null) {
next.handle(exchange);
} else {
throw new IllegalArgumentException("No handler found for " + exchange.getService().getId());
}
}
}
ProductQualitySupport.java
package com.iblog.pattern.responsibilityChain.handler;
import com.iblog.pattern.responsibilityChain.Category;
import com.iblog.pattern.responsibilityChain.ServiceExchange;
public class ProductQualitySupport implements ServiceHandler {
private final ServiceHandler next;
public ProductQualitySupport(ServiceHandler next) {
this.next = next;
}
@Override
public void handle(ServiceExchange exchange) {
if (exchange.getService().getCategory() == Category.PRODUCT_QUALITY) {
exchange.getSolution().setSolved(true);
exchange.getSolution().setSulvedBy(this.getClass().getSimpleName());
return;
}
if (next != null) {
next.handle(exchange);
} else {
throw new IllegalArgumentException("No handler found for " + exchange.getService().getId());
}
}
}
ReturnSupport.java
package com.iblog.pattern.responsibilityChain.handler;
import com.iblog.pattern.responsibilityChain.Category;
import com.iblog.pattern.responsibilityChain.ServiceExchange;
public class ReturnSupport implements ServiceHandler {
private final ServiceHandler next;
public ReturnSupport(ServiceHandler next) {
this.next = next;
}
@Override
public void handle(ServiceExchange exchange) {
if (exchange.getService().getCategory() == Category.RETURN_SERVICE) {
exchange.getSolution().setSolved(true);
exchange.getSolution().setSulvedBy(this.getClass().getSimpleName());
return;
}
if (next != null) {
next.handle(exchange);
} else {
throw new IllegalArgumentException("No handler found for " + exchange.getService().getId());
}
}
}
OtherSupport.java
package com.iblog.pattern.responsibilityChain.handler;
import com.iblog.pattern.responsibilityChain.ServiceExchange;
/**
* End of responsibility chain.
*/
public class OtherSupport implements ServiceHandler {
@Override
public void handle(ServiceExchange exchange) {
// handle other categories exclude null
if (exchange.getService().getCategory() == null) {
throw new IllegalArgumentException("Invalid Service "
+ exchange.getService().getId()
+ " that missing category");
}
exchange.getSolution().setSolved(true);
exchange.getSolution().setSulvedBy(this.getClass().getSimpleName());
}
}
测试应用
HandlerRegistryTest.java
package com.iblog.pattern.responsibilityChain;
import com.iblog.pattern.responsibilityChain.handler.*;
import org.junit.Test;
import static org.junit.Assert.*;
public class HandlerRegistryTest {
private final ServiceSupport support = HandlerRegistry.get();
@Test(expected = IllegalArgumentException.class)
public void testNull() {
Service service = new Service.Builder(null)
.setId("1")
.setDescription("Test case: category is null")
.build();
Solution.Builder solution = new Solution.Builder(service.getId());
ServiceExchange exchange = new ServiceExchange(service, solution);
support.handle(exchange);
}
@Test
public void testOther() {
Service service = new Service.Builder(Category.OTHER)
.setId("1")
.setDescription("Test case: other")
.build();
Solution.Builder builder = new Solution.Builder(service.getId());
ServiceExchange exchange = new ServiceExchange(service, builder);
support.handle(exchange);
Solution solution = exchange.getSolution().build();
assertEquals(OtherSupport.class.getSimpleName(), solution.getSolvedBy());
assertTrue(solution.isSolved());
}
@Test
public void testPreSales() {
Service service = new Service.Builder(Category.PRE_SALES_PRODUCT_CONSULTATION)
.setId("1")
.setDescription("Test case: pre-sales")
.build();
Solution.Builder builder = new Solution.Builder(service.getId());
ServiceExchange exchange = new ServiceExchange(service, builder);
support.handle(exchange);
Solution solution = exchange.getSolution().build();
assertEquals(PreSalesSupport.class.getSimpleName(), solution.getSolvedBy());
assertTrue(solution.isSolved());
}
@Test
public void testPromotions() {
Service service = new Service.Builder(Category.PROMOTIONS)
.setId("1")
.setDescription("Test case: promotions")
.build();
Solution.Builder builder = new Solution.Builder(service.getId());
ServiceExchange exchange = new ServiceExchange(service, builder);
support.handle(exchange);
Solution solution = exchange.getSolution().build();
assertEquals(PromotionsSupport.class.getSimpleName(), solution.getSolvedBy());
assertTrue(solution.isSolved());
}
@Test
public void testProductQuality() {
Service service = new Service.Builder(Category.PRODUCT_QUALITY)
.setId("1")
.setDescription("Test case: product quality")
.build();
Solution.Builder builder = new Solution.Builder(service.getId());
ServiceExchange exchange = new ServiceExchange(service, builder);
support.handle(exchange);
Solution solution = exchange.getSolution().build();
assertEquals(ProductQualitySupport.class.getSimpleName(), solution.getSolvedBy());
assertTrue(solution.isSolved());
}
@Test
public void testCommoditySupply() {
Service service = new Service.Builder(Category.COMMODITY_SUPPLY_CHAIN)
.setId("1")
.setDescription("Test case: commodity supply")
.build();
Solution.Builder builder = new Solution.Builder(service.getId());
ServiceExchange exchange = new ServiceExchange(service, builder);
support.handle(exchange);
Solution solution = exchange.getSolution().build();
assertEquals(CommoditySupplySupport.class.getSimpleName(), solution.getSolvedBy());
assertTrue(solution.isSolved());
}
@Test
public void testReturn() {
Service service = new Service.Builder(Category.RETURN_SERVICE)
.setId("1")
.setDescription("Test case: return")
.build();
Solution.Builder builder = new Solution.Builder(service.getId());
ServiceExchange exchange = new ServiceExchange(service, builder);
support.handle(exchange);
Solution solution = exchange.getSolution().build();
assertEquals(ReturnSupport.class.getSimpleName(), solution.getSolvedBy());
assertTrue(solution.isSolved());
}
}
Jdk中的责任链模式
Github:pattern-example
网友评论