This is a reading note for Java 8 In Action, which takes about 5 minutes to read.

It’s a bit clickbait, but that’s certainly how I feel about using Lambda expressions recently. Design patterns are a summary of some good lessons and routines from the past, but good language features can keep developers from thinking about them. Common object-oriented design patterns include the policy pattern, the template approach, the observer pattern, the chain of responsibility pattern, and the factory pattern. Using Lambda expressions (functional programming thinking) helps avoid the fixed code in object-oriented development. Below, we select two cases of strategy mode and responsibility chain mode for analysis.

Case 1: The policy pattern

When we have different solutions to a problem and we don’t want the customer to be aware of the details of those solutions, the strategic pattern is appropriate. The strategy pattern consists of three parts:

  • Problem solving algorithm (Strategy in the figure above);
  • Concrete implementations of one or more of these algorithms (ConcreteStrategyA, ConcreteStrategyB, and ConcreteStrategyC in the figure above)
  • One or more customer usage scenarios (ClientContext in the figure above)

Object-oriented thinking

First define the policy interface, representing the sorting policy:

public interface ValidationStrategy {
    boolean execute(String s);
}
Copy the code

Then define concrete implementation classes (i.e., different sorting algorithms) :

public class IsAllLowerCase implements ValidationStrategy {
    @Override
    public boolean execute(String s) {
        return s.matches("[a-z]+"); }}public class IsNumberic implements ValidationStrategy {
    @Override
    public boolean execute(String s) {
        return s.matches("\\d+"); }}Copy the code

Finally, define the customer usage scenario, as shown in the figure below. Validators are the context used to provide services to customers. Each Valiator object encapsulates a specific Strategy object. In practice, we can upgrade customer services by changing the specific Strategy object without requiring the customer to upgrade.

public class Validator {

    private final ValidationStrategy strategy;

    public Validator(ValidationStrategy strategy) {
        this.strategy = strategy;
    }

    /** * Interface to the customer */
    public boolean validate(String s) {
        returnstrategy.execute(s); }}public class ClientTestDrive {

    public static void main(String[] args) {
        Validator numbericValidator = new Validator(new IsNumberic());
        boolean res1 = numbericValidator.validate("7780");
        System.out.println(res1);

        Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
        boolean res2 = lowerCaseValidator.validate("aaaddd"); System.out.println(res2); }}Copy the code

Functional programming ideas

If you use Lambda expressions, you can see that ValidationStrategy is a function interface (and has the same function description as Predicate). Instead of defining the above implementation classes, you can simply replace them with the following code: The reason is that there is already some encapsulation of these classes inside Lambda expressions.

public class ClientTestDrive {

    public static void main(String[] args) {
        Validator numbericValidator = new Validator((String s) -> s.matches("\\d+"));
        boolean res1 = numbericValidator.validate("7789");
        System.out.println(res1);

        Validator lowerCaseValidator = new Validator((String s) -> s.matches("[a-z]+"));
        boolean res2 = lowerCaseValidator.validate("aaaddd"); System.out.println(res2); }}Copy the code

Case 2: Chain of responsibility model

In some scenarios where there is a series of tasks to be done on an object, each of which is done by a different class, the chain of responsibility pattern is appropriate. The main components of the responsibility chain model include three parts:

  • An abstract class that manages sequences of operations, in which an object records the successors of the current object;
  • Specific action objects, organized in a linked list
  • A client-side component that uses this pattern only needs to interact with one component, rather than coupling to many action objects.

Object-oriented thinking

Let’s start with the definition of an abstract class, ProcessingObject, in which the succeeded operation fields are managed; The Handle interface is used to provide services externally. HandleWork is the operation method that actually processes the object.

public abstract class ProcessingObject<T> {

    protected ProcessingObject<T> successor;
    
    public void setSuccessor(ProcessingObject<T> successor) {
        this.successor = successor;
    }

    public T handler(T input) {
        T r = handleWork(input);
        if(successor ! =null) {
            return successor.handler(r);
        }
        return r;
    }

    abstract protected T handleWork(T input);
}
Copy the code

You can then define two concrete action objects, as shown in the code below. PS: The replaceAll method used in the Java 8 Field book is not appropriate. This point can be referred to our previous article — 020: give several examples of the String API and case.

public class HeaderTextProcessing extends ProcessingObject<String> {
    @Override
    protected String handleWork(String input) {
        return "From Raoul, Mario and Alan: "+ input; }}public class SpellCheckerProcessing extends ProcessingObject<String> {
    @Override
    protected String handleWork(String input) {
        return input.replace("labda"."lambda"); }}Copy the code

Finally, you can combine these two concrete operation-class objects into an operation sequence in the Client, as shown in the following code:

public class Client {
    public static void main(String[] args) {
        ProcessingObject<String> p1 = new HeaderTextProcessing();
        ProcessingObject<String> p2 = new SpellCheckerProcessing();

        p1.setSuccessor(p2);

        String result = p1.handler("Aren't labdas really sexy? !!"); System.out.println(result); }}Copy the code

Functional programming ideas

If you use functional programming thinking, then the chain of responsibilities pattern is straightforward — y=f(x) and z=g(x), both of which deal with x, so if you combine these two functions together, you get r=f(g(x)), That is, you can use addThen in Lambda expressions to concatenate multiple processes.

public class ClientWithLambda {
    public static void main(String[] args) {
        UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;

        UnaryOperator<String> spellCheckProcessing = (String text) -> text.replace("labda"."lambda");

        Function<String, String> function = headerProcessing.andThen(spellCheckProcessing);

        String result = function.apply("Aren't labdas really sexy? !!");
        System.out.println(result);

        UnaryOperator<String> hhhhhProcessing = (String text) -> text.concat("hhhh");
        Function<String, String> function1 = function.andThen(hhhhhProcessing);
        String result1 = function1.apply("Aren't labdas really sexy? !!"); System.out.println(result1); }}Copy the code

The above is the chain of responsibility pattern implemented using Java’s native Lambda expressions. We can also use the previous article — Vavr: Lets you write the VAVR library in Java just as you would write Scala, as shown below:

public class ClientWithVavr {
    public static void main(String[] args) {
        Function1<String, String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;
        Function1<String, String> specllCheckProcessing = (String text) -> text.replace("labda"."lambda");

        Function1<String, String> function = headerProcessing.compose(specllCheckProcessing);
        String result = function.apply("Aren't labdas really sexy? !!"); System.out.println(result); }}Copy the code

conclusion

It can be seen that functional programming thinking is different from object-oriented programming thinking, which is more expressive. Therefore, as a developer, it is time to learn functional programming thinking seriously. As a Java developer, I am going to start with Lambda expressions. Then try learning the functional programming features of Scala or Kotlin.

The resources

  1. Java Programming In Action
  2. Zen of Design Patterns

This issue focuses on topics such as backend technology, JVM troubleshooting and optimization, Java interview questions, personal growth and self-management, providing readers with front-line developer work and growth experience, looking forward to your harvest here.