Functional programming lets you forget design patterns

Posted by hearn on Sat, 06 Jul 2019 18:05:07 +0200

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

It's a bit of a headline party, but that's what I feel when I recently used Lambda expressions.Design patterns are a summary of some good experience and routines from the past, but good language features allow developers to bypass them.Common object-oriented design patterns are strategy mode, template method, observer mode, responsibility chain mode, and factory mode. Using Lambda expressions (functional programming thinking) helps avoid fixed code in object-oriented development.Below we select two cases, the strategy model and the responsibility chain model, for analysis.

Case 1: Strategic Patterns

When we have different solutions to a problem, we don't want our customers to be aware of the details of those solutions. This is a good case to use a strategy model.The strategy pattern consists of three parts:

  • The algorithm to solve the problem (trategy in the figure above);
  • Specific implementation of one or more of these algorithms (ConcreteStrategyA, ConcreteStrategyB, and ConcreteStrategyC in the figure above)
  • One or more customer usage scenarios (ClintContext in the figure above)

Object-oriented thinking

First, define the policy interface to represent the sorting policy:

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

Then define specific implementation classes (that is, different sort 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+");
    }
}

Finally, define the customer usage scenario, as shown in the code below.Validator is the context used to provide services to customers. Each Valiator object encapsulates a specific Strategy object. In practice, we can upgrade customer service by replacing a specific Strategy object, and there is no need for customers to upgrade.

public class Validator {

    private final ValidationStrategy strategy;

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

    /**
     * Interface to Customer
     */
    public boolean validate(String s) {
        return strategy.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);
    }
}

Functional programming ideas

If you consider using a Lambda expression, you will find that ValidationStrategy is a function interface (which also has the same function description as Predicate<String>), then you do not need to define the implementation classes above, you can simply replace them with the code below, because these classes are already built into the Lambda expressionFixed encapsulation.

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

Case 2: Responsibility Chain Model

In some scenarios, a series of tasks need to be done on an object, which are done by different classes, so the responsibility chain model is more appropriate.The main components of the responsibility chain model include three:

  • An abstract class that manages the sequence of operations in which an object records the subsequent operations of the current object.
  • Specific operation objects that are organized as a list of chains
  • A client component that uses this pattern, which only needs to deal with one component and does not need to be coupled with many operation objects.

Object-oriented thinking

First, let's look at the definition of an abstract class, ProcessingObject, in which the successor field manages the object's subsequent operations; the handle interface serves as the interface for external services; and the handleWork as the method of operation for the actual processing 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);
}

Next, you can define two specific action objects, as shown in the code below.PS: It is not appropriate to use the replaceAll method in the Java 8 Actual Warfare book here. Refer to our previous articles for this point: 020: cite a few String API s and cases
).

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

Finally, you can form a sequence of operations from the above two specific operation class objects in Client, see the code below:

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

Functional programming ideas

If you use functional programming thinking, then the responsibility chain pattern is straightforward - both y=f(x) and z=g(x) are methods for handling X. If you combine the two functions, you will get the situation of 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);
    }
}

The above is a responsibility chain pattern implemented using Java's native Lambda expression, which we can also use in the previous article: vavr: let you write Java like Scala
) The vavr library implementation described in the following code:

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

summary

As you can see, functional programming thinking is different from object-oriented programming thinking in that it is more expressive, so it is time for developers to learn functional programming thinking carefully. As Java developers, I am ready to start with Lambda expressions, and then try to learn Scala or Kotlin.Functional expressions in words become attributes.

Reference material

  1. Java Programming Actual Warfare
  2. Zen in Design Mode

This number focuses on topics such as back-end technology, JVM problem solving and optimization, Java interview questions, personal growth and self-management, providing readers with first-line developer work and growth experience, and we look forward to seeing what you can do here.

Topics: Java Lambda Programming Scala