好吧,当有“管道”、“操作序列”等时,首先想到的设计模式是责任链,如下所示
并为您提供以下好处:
- 允许您在必要时(例如在运行时)添加新的处理程序,而无需修改其他处理程序和处理逻辑(SOLID 的开放/封闭原则)
- 允许处理程序在必要时停止处理请求
- 允许您将处理程序的处理逻辑彼此分离(SOLID 的单一职责原则)
- 允许您定义处理程序的顺序以在处理程序本身之外处理请求
实际使用的一个例子是Servlet filters,您可以在其中调用doFilter(HttpRequest, HttpResponse, FilterChain) 来调用下一个处理程序
protected void doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) {
if (haveToInvokeNextHandler) {
chain.doFilter(req, resp);
}
}
如果使用经典的责任链模式,您的处理管道可能如下所示:
API
public class StepContext {
private Map<String, Object> attributes = new HashMap<>();
public <T> T getAttribute(String name) {
(T) attributes.get(name);
}
public void setAttribute(String name, Object value) {
attributes.put(name, value);
}
}
public interface Step {
void handle(StepContext ctx);
}
public abstract class AbstractStep implements Step {
private Step next;
public AbstractStep() {
}
public AbstractStep(Step next) {
this.next = next;
}
protected void next(StepContext ctx) {
if (next != null) {
next.handle(ctx);
}
}
}
实施
public class Step1 extends AbstractStep {
public Step1(Step next) {
super(next);
}
public void handle(StepContext ctx) {
String action1 = ctx.getAttribute("action1");
List<String> output1 = doSomething(action1);
ctx.setAttribute("output1", output1);
next(ctx); // invoke next step
}
}
public class Step2 extends AbstractStep {
public Step2(Step next) {
super(next);
}
public void handle(StepContext ctx) {
String action2 = ctx.getAttribute("action2");
List<String> output1 = ctx.getAttribute("output1");
List<String> output2 = doSomething(output1, action2);
ctx.setAttribute("output2", output2);
next(ctx); // invoke next step
}
}
public class Step3 extends AbstractStep {
public Step3(Step next) {
super(next);
}
public void handle(StepContext ctx) {
String action2 = ctx.getAttribute("action2");
List<String> output2 = ctx.getAttribute("output2");
persist(output2);
next(ctx); // invoke next step
}
}
客户端代码
Step step3 = new Step3(null);
Step step2 = new Step2(step3);
Step step1 = new Step1(step2);
StepContext ctx = new StepContext();
ctx.setAttribute("action1", action1);
ctx.setAttribute("action2", action2);
step1.handle(ctx);
此外,所有这些东西都可以通过删除相应的 next 引用来简化为彼此分离的处理程序链,以防您的处理管道必须始终调用所有可用步骤而不控制调用的必要性上一个:
API
public class StepContext {
private Map<String, Object> attributes = new HashMap<>();
public <T> T getAttribute(String name) {
(T) attributes.get(name);
}
public void setAttribute(String name, Object value) {
attributes.put(name, value);
}
}
public interface Step {
void handle(StepContext ctx);
}
实施
public class Step1 implements Step {
public void handle(StepContext ctx) {
String action1 = ctx.getAttribute("action1");
List<String> output1 = doSomething(action1);
ctx.setAttribute("output1", output1);
}
}
public class Step2 implements Step {
public void handle(StepContext ctx) {
String action2 = ctx.getAttribute("action2");
List<String> output1 = ctx.getAttribute("output1");
List<String> output2 = doSomething(output1, action2);
ctx.setAttribute("output2", output2);
}
}
public class Step3 implements Step {
public void handle(StepContext ctx) {
String action2 = ctx.getAttribute("action2");
List<String> output2 = ctx.getAttribute("output2");
persist(output2);
}
}
客户端代码
注意,在 Spring 框架的情况下(刚刚注意到 @Autowired 注释),客户端代码可能会更加简化,因为 @Autowired 注释可用于将相应类型的所有 bean 注入相应的集合中。
documentation 声明如下:
自动装配数组、集合和映射
如果是数组、Collection 或 Map 依赖类型,容器会自动装配与声明的值类型匹配的所有 bean。为此,必须将映射键声明为 String 类型,该类型将解析为相应的 bean 名称。考虑到目标组件的 Ordered 和 @Order 值,将对此类容器提供的集合进行排序,否则将遵循它们在容器中的注册顺序。或者,单个匹配的目标 bean 也可能是一般类型的 Collection 或 Map 本身,因此被注入。
public class StepsInvoker {
// spring will put all the steps into this collection in order they were declared
// within the spring context (or by means of `@Order` annotation)
@Autowired
private List<Step> steps;
public void invoke(String action1, String action2) {
StepContext ctx = new StepContext();
ctx.setAttribute("action1", action1);
ctx.setAttribute("action2", action2);
steps.forEach(step -> step.handle(ctx))
}
}