In this series, we will consider how to implement design patterns using the lambda expression / Stream API introduced in Java 8. Previous article covered the Template Method pattern. The sample code is posted on GitHub.
Chain of Responsibility
is translated as a" chain of responsibility ", but as the name implies, it is a pattern that solves problems by passing responsibility one after another in a chain of objects.
The static structure of the class is shown in the figure below.
Actually, it is a structure in which multiple handlers are connected, such as ConcreteHandler1 instance
-> ConcreteHandler2 instance
-> ...-> ConcreteHandlerN instance
.
The client sends a request to the first handler in the chain, and if the first handler can solve it, it returns the result as it is, but if the problem cannot be solved by itself, it delegates the processing to the next handler.
This flow propagates along the chain, and the handler that finally solved the problem returns the processing result to the previous handler, that handler returns to the previous handler, and so on, and finally to the client. The processing result is returned.
The client does not have to worry about which handler solved the problem.
This sample is based on approval of approval
. The amount that can be settled increases as the position goes up, such as section manager
-> department manager
-> business department manager
, and if it is within your own approval authority, you approve it yourself, otherwise you approve it to a higher-ranking officer. Imagine a scenario where you delegate processing.
The Chain of Responsibility
pattern first requires building a chain of objects responsible for the problem.
If you can solve it yourself, return the result. Otherwise, the behavior of calling the delegation destination process can be shared, so it is standard to define it in an abstract class. Also, since each resolution process is different for each concrete class, you will probably use the
Template Method pattern. In the sample code below, ʻapprove (int)
is the template method and doApprove (int)
is the hook method.
The second null judgment in ʻapprove (int)may be avoided by using the
Null Object` pattern, but it is omitted here.
AbstractApprover.java
public abstract class AbstractApprover {
private AbstractApprover next;
protected String name;
public AbstractApprover(String name, AbstractApprover next) {
this.name = name;
this.next = next;
}
public final Approval approve(int amount) {
Approval approval = doApprove(amount);
if (approval != null) {
return approval;
} else if (next != null) {
return next.approve(amount);
} else {
System.out.println(String.format("I can't approve.%,3d yen", amount));
return null;
}
}
protected abstract Approval doApprove(int amount);
}
The implementation example of the subclass is as follows.
Kacho.java
public class Kacho extends AbstractApprover {
public Kacho(String name, AbstractApprover next) {
super(name, next);
}
@Override
protected Approval doApprove(int amount) {
if (amount <= 10000) {
System.out.println(String.format("Chief approval(%s) %,3d yen", name, amount));
return new Approval(name, amount);
} else {
return null;
}
}
}
Assembling and executing the chain is as follows. (I assemble it myself for test code, but I'll actually get it from Factory
)
Usage
JigyoBucho jigyoBucho = new JigyoBucho("Yamada", null);
Bucho bucho = new Bucho("Tanaka", jigyoBucho);
Kacho kacho = new Kacho("Suzuki", bucho);
Approval approval = kacho.approve(10000);
assertEquals("Suzuki", approval.getApprover());
approval = kacho.approve(100000);
assertEquals("Tanaka", approval.getApprover());
approval = kacho.approve(1000000);
assertEquals("Yamada", approval.getApprover());
approval = kacho.approve(1000001);
assertNull(approval);
Since it is troublesome to define and implement subclasses for differences only in conditional judgment such as amount of money as in this example, express it in a lambda expression and prepare a method to assemble the chain together. To. Since Java 8 makes it possible to define default methods and static methods in an interface, it can be defined in one interface as follows.
Approvable.java
@FunctionalInterface
public interface Approvable {
Approval approve(int amount);
default Approvable delegate(Approvable next) {
return (amount) -> {
final Approval result = approve(amount);
return result != null ? result : next.approve(amount);
};
}
static Approvable chain(Approvable... approvables) {
return Stream.concat(Stream.of(approvables), Stream.of(tail()))
.reduce((approvable, next) -> approvable.delegate(next))
.orElse(null);
}
static Approvable approver(String title, String name, Predicate<Integer> predicate) {
return amount -> {
if (predicate.test(amount)) {
System.out.println(String.format("%s approval(%s) %,3d yen", title, name, amount));
return new Approval(name, amount);
} else {
return null;
}
};
}
static Approvable tail() {
return amount -> {
System.out.println(String.format("I can't approve.%,3d yen", amount));
return null;
};
}
}
The contents of the above code are explained below.
First, the responsibility to settle a given amount
is defined as the delegate (int): Approval
method that receives the amount and returns the approval result.
In order to implement this process in a lambda expression for each job title such as section chief or department manager, define this process as a functional interface that has it as the only method and add the @FunctionalInterface
annotation.
@FunctionalInterface
public interface Approvable {
Approval approve(int amount);
The following default method delegate (Approvable)
is a method prepared for function synthesis.
java.util.Function <T, R>
has methods for function composition called compose
and ʻandThen, so you can use these if you simply connect the processes. However, in the case of the
Chain of Responsibility pattern, you need to ** stop the propagation of processing at that point if you can solve it **, so you need to prepare your own method. It returns in a lambda expression the behavior of first calling its own process (ʻapprove
) and then calling the next process if the result is null.
default Approvable delegate(Approvable next) {
return (amount) -> {
final Approval result = approve(amount);
return result != null ? result : next.approve(amount);
};
}
The next static method, chain (Approvable ...)
, assembles the chain.
A Stream is formed from the array of ʻApprovablereceived with variable length arguments, and by reducing it, it is combined into one function and returned. At this time, use the
delegate` method mentioned earlier.
static Approvable chain(Approvable... approvables) {
return Stream.concat(Stream.of(approvables), Stream.of(tail()))
.reduce((approvable, next) -> approvable.delegate(next))
.orElse(null);
}
The following ʻapprover (String, String, Predicate is the
Factory Method for generating the actual implementation of ʻApprovable
(the section chief or department manager).
static Approvable approver(String title, String name, Predicate<Integer> predicate) {
return amount -> {
if (predicate.test(amount)) {
System.out.println(String.format("%s approval(%s) %,3d yen", title, name, amount));
return new Approval(name, amount);
} else {
return null;
}
};
}
The following example shows how to assemble and execute a chain using ʻApprovable`.
Usage
Approvable kacho = Approvable.approver("Manager", "Suzuki", amount -> amount <= 10000);
Approvable bucho = Approvable.approver("Director", "Tanaka", amount -> amount <= 100000);
Approvable jigyoBucho = Approvable.approver("Division Manager", "Yamada", amount -> amount <= 1000000);
Approvable cor = Approvable.chain(kacho, bucho, jigyoBucho);
Approval approval = cor.approve(10000);
assertEquals("Suzuki", approval.getApprover());
approval = cor.approve(100000);
assertEquals("Tanaka", approval.getApprover());
approval = cor.approve(1000000);
assertEquals("Yamada", approval.getApprover());
approval = cor.approve(1000001);
assertNull(approval);
With good use of generic types, the properties of Chain of Responsiblity
can be generalized and defined.
Here, we define its properties as follows and try to generalize it.
The functional interface corresponding to ʻApprovable` in the previous section is defined as follows. Since the type parameters are included, it will be a little difficult to read, but the processing contents are the same.
Delegatable.java
@FunctionalInterface
public interface Delegatable<Q, R> {
R call(Q q);
default Delegatable<Q, R> delegate(Delegatable<Q, R> next) {
return q -> {
final R result = call(q);
return result != null ? result : next.call(q);
};
}
}
And the class that implements the above properties is exactly named ChainOfResponsibility <Q, R>
.
ChainOfResponsibility
public class ChainOfResponsibility<Q, R> {
private List<Delegatable<Q, R>> chain;
public ChainOfResponsibility() {
super();
chain = new ArrayList<>();
}
public ChainOfResponsibility<Q, R> add(Function<Q, R> func) {
chain.add(q -> func.apply(q));
return this;
}
public ChainOfResponsibility<Q, R> tail(Consumer<Q> tail) {
chain.add(q -> {
tail.accept(q);
return null;
});
return this;
}
public Function<Q, R> build() {
Optional<Delegatable<Q, R>> head = chain.stream()
.reduce((delegatable, next) -> delegatable.delegate(next));
return head.isPresent() ? q -> head.get().call(q) : null;
}
}
The usage example is as follows.
Usage
@Test
public void test() {
Function<Integer, Approval> kacho = approver("Manager", "Suzuki", amount -> amount <= 10000);
Function<Integer, Approval> bucho = approver("Director", "Tanaka", amount -> amount <= 100000);
Function<Integer, Approval> jigyoBucho = approver("Division Manager", "Yamada", amount -> amount <= 1000000);
Function<Integer, Approval> head = new ChainOfResponsibility<Integer, Approval>()
.add(kacho)
.add(bucho)
.add(jigyoBucho)
.tail(amount -> System.out.println(String.format("I can't approve.%,3d yen", amount)))
.build();
Approval approval = head.apply(10000);
assertEquals("Suzuki", approval.getApprover());
approval = head.apply(100000);
assertEquals("Tanaka", approval.getApprover());
approval = head.apply(1000000);
assertEquals("Yamada", approval.getApprover());
approval = head.apply(1000001);
assertNull(approval);
}
private Function<Integer, Approval> approver(String title, String name, Predicate<Integer> predicate) {
return amount -> {
if (predicate.test(amount)) {
System.out.println(String.format("%s approval(%s) %,3d yen", title, name, amount));
return new Approval(name, amount);
} else {
return null;
}
};
}
The propagation of processing in the Chain of Responsibility
pattern was successfully implemented by performing function synthesis.
In addition, the pattern can be generalized by using the generic type. Functional programming with lambda expressions is very powerful, isn't it?
Recommended Posts