,就像星爷多年前说的“看代码,像个链条”。什么?他不是这么说的吗,可能是我记错了。您应该已经猜到,在本文中,我们将讨论责任链设计模式。这种模式并不流行,至少在四人组定义的模式中不流行。但是现代依赖注入框架允许我们以巧妙和新颖的方式实现这种模式,让我们来看看。介绍性声明:此模式没有任何新内容。我的一个同事前几天刚用过,我已经用过很多次了。这篇文章的灵感来自于我最近遇到的一个问题。下面我们就来说说吧。我没有意识到这个问题可以用这个模式来解决。传统模式责任链模式是一种行为设计模式,最早出现在四人帮的设计模式一书中。该模式的目的是避免请求的发送者和接收者之间的耦合,并为多个对象提供处理请求的机会。接收对象串联,请求在链上传递,直到被对象处理。类的关系图如下示例:松耦合是通过定义一个标准接口来实现的,可以用来响应客户端的请求。在上图中,表现为Handler抽象类型。多个类响应请求的能力可以通过创建链式类并继承上述接口来实现。每个类都拥有链中下一个节点的一个实例。后继属性满足范围。调用时,每个处理程序都会确定它是否能够处理请求。如果是,则执行请求的操作,这里我们可以根据请求的转发规则,实现多种不同的处理方式。一旦ConcreteHandler声明它可以处理请求,我们就可以实施规则来阻止请求通过链。在这种情况下,handleRequest方法的实现如下:链中下一个处理程序的请求,无论当前处理程序是否可以处理它。if(/*可以成功处理请求*/){//处理请求}successor.handleRequest(request);建链的操作应该和下面类似。处理器链=newConcreteHandler1(newConcreteHandler2(newConcreteHandler3()));chain.handleRequest(请求);在JDK的内部实现中,至少有两个地方使用了这种模式:日志机制的实现:java.util.logging.Logger#log()http请求过滤机制和Servlet响应规范的实现:javax.servlet.Filter#doFilter()依赖注入的出现就像许多其他情况一样,依赖注入模式的出现改变了一切。让我们看看依赖注入功能如何使责任链模式现代化。首先,我们需要一个所有依赖注入库都实现的特性:多重绑定。基本上,它可以提供一个类型的所有子类型的实例,只需注入该类型的集合。例如,下面的类型系统:interfaceShop{}classPetShopimplementsShop{}classGroceryimplementsShop{}classDrugstoreimplementsShop{}//等等……现在,我们定义了一个新类型ShoppingCenter,它有每个该类型的Shop实例的子类。使用依赖注入,我们可以通过在ShoppingCenter中注入一个Shop集合来实现这一点。classShoppingCenter{privatefinalSet商店;@InjectpublicvoidShoppingCenter(Setshops){this.shops=shops;}}//使用商店的类主体}真的很简单!显然,每个依赖注入库都有自己的配置来处理这种情况。在Spring中,使用自动发现功能,你只需要一点配置。在Guice中,稍微复杂一些,但最终结果是一样的。责任链模式现代实现的简要总结:我们已经看到了责任链模式的典型形式;我们已经看到了依赖注入库提供的多重绑定特性;最后,我们看到了如何一起使用这两个概念。首先,我们需要对原始责任链设计模式进行稍微不同的实现。下面介绍一种新型的ChainHandler。该类型的职责是拥有整条链,并向客户端暴露访问链提供的操作功能的接口。类ChainHandler{privatefinalSet处理程序;@InjectpublicvoidChainHandler(Sethandlers){this.handlers=handlers;}//将请求传递给链中的每个处理程序publicvoidhandle(finalRequestrequest){handlers.forEach(h->h.handle(request));}}利用依赖注入来添加Handler实现,而无需更改现有代码。这意味着我们实际上不需要执行回归测试。另一方面,将Handlers的执行放在链中有些困难(但并非不可能)。警告与许多其他模式一样,重要的是要关注每个类在构造模式中的作用。您会给每个特定的Handler赋予什么功能?你会把应用的业务逻辑直接放在Handler中吗?首先,我们中的许多人都会提供上述解决方案,这并非完全错误。但是这种设计限制了代码的重用,违反了单一职责原则(SingleResponsibilityPrinciple)。例如,我们需要实现一个系统来完成金融业务中的信息,完成操作使用责任链模型。可以插入的一项补充信息是从IBAN(国际银行帐号)或BIC(银行代码)得出的收款人所在国家/地区。然后我们定义一个CountryPayeeEnricher。第一眼,我们可以直接在CountryPayeeEnricher中写代码完成国家信息。但是,如果我们需要在我们的应用程序(或其他应用程序)的其他地方重用此功能怎么办?遵循组合原则是更好的解决方案,将代码放入专有类中,例如PayeeService:遗漏...}classCountryPayeeEnricherimplementsEnrichment{privatePayeeServicepayeeService;@InjectpublicvoidCountryPayeeEnricher(PayeeServicepayeeService){this.payeeService=payeeService;}publicvoidhandle(Transactiontx){Countrycountry=payeeService.deriveCountryFromPay.getPayee());tx.setCountry(国家);//...或类似这样的东西}}这样,我们最终得到两种不同职责的类型:PayeeService类型,提供可重用的直接联系收款人信息的服务。CountryPayeeEnricher类型替换了先前类型提供的服务的标准条目。Scala方式为了安全起见,我还想讨论在Scala语言中实现责任链模式。与许多其他设计模式一样,该语言在内部实现了责任链模式:部分功能。在理论层面上,偏函数是定义域中值的子集的函数。在Scala中,这个函数有一个特殊的类型——PartialFunction[T,V]在Scala中使用模式匹配(patternmatching)语句来定义偏函数,在下面的例子中,fraction的默认值为0。valfraction:PartialFunction[Int,Int]={cased:Intifd!=0=>42/d}如果有多个定义集,你可以有多个case子句。如果您将每个case子句视为一个满意的案例(责任链中的处理程序,还记得吗?)以便应用该功能,您将再次使用责任链:caseclassRequest(valvalue:String){/*...*/}valsomeStupidFunction:PartialFunction[Request,String]={caseRequest(42)=>“最终答案”caseRequest(0)=>“你什么都不知道,JohnSnow”caseRequest(666)=>"这里发生了一些奇怪的事情”//。..}紧接着,部分函数可以被视为许多处理程序的链。显然,通过以这种方式使用责任链模式,您必须遵守一些额外的约束。事实上:你不能在每个处理程序中存储元数据你不能从链中删除处理程序你不能显式检查处理程序或漂亮地打印它)工作得很好。