Java8 Lambda Expression & Stream Design Pattern Rethinking --Chain of Responsibility Pattern -

Introduction

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.

This pattern

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.

cor.png

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.

Subject

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.

Traditional implementation

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 theNull 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);

Implementation method using lambda expression

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 thedelegate` 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 theFactory 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);

Development: Try to generalize

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;
            }
        };
    }

Summary

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

Java8 Lambda Expression & Stream Design Pattern Rethinking --Chain of Responsibility Pattern -
Java8 Lambda Expression & Stream Design Pattern Rethinking --Null Object Pattern -
Design pattern ~ Chain of Responsibility ~
Java8 Lambda expression & Stream design pattern reconsideration --Command pattern -
Java8 Lambda expression & Stream design pattern reconsideration --Template Method pattern -
Rethinking design patterns with Java8 lambda expressions & Stream --Builder pattern -
Chain of Responsibility Pattern
Chain Of Responsibility pattern
Java8 stream, lambda expression summary
About Lambda, Stream, LocalDate of Java8
Java design pattern
[Java] Lambda expression
Java lambda expression
java neutral lambda expression 1
Java lambda expression variations
Java 8 lambda expression Feature
java lambda expression memo
Java lambda expression [memo]
Studying Java 8 (lambda expression)
Review java8 ~ Lambda expression ~
Java lambda expression again
Java Lambda Command Pattern
Java design pattern summary
Use Java lambda expressions outside of the Stream API
Getting Started with Legacy Java Engineers (Stream + Lambda Expression)
[Java] Functional interface / lambda expression
[Java11] Stream Summary -Advantages of Stream-
[Design pattern] Java core library
[Java] Summary of design patterns
Java basic learning content 9 (lambda expression)
Application example of design pattern (No. 1)
Basic processing flow of java Stream
What is a lambda expression (Java)
Java beginner design pattern (Factory Method pattern)
The origin of Java lambda expressions
Comparison of thread implementation methods in Java and lambda expression description method
Now let's recap the Java lambda expression
Nowadays Java lambda expressions and Stream API
I've reviewed Java's lambda expression, stream API, six months after I started Java.