Can only design patterns, may not be enough now…

With patterns, we can make a solution reusable rather than reinventing the wheel. With patterns,you can use the solution a million times over,without ever doing it the same way twice. — Christopher Alexander

Writing code is easy, writing elegant code is hard, but writing code that is easy to maintain, easy to extend, and well-structured should be the goal of every developer. Learning design patterns and using them properly can bring us one step closer to this goal. However in the practical work if to apply model for related code, long-term development, or found in the module will add a new interface or class definition, multiple modules in this kind of disease, especially a big project need the same pattern is more outstanding, that how to solve the design patterns to reduce the introduction of a similar problem, As a practical reference, this article will use the Java language (and others alike) to leverage Lambda expressions to improve object-oriented design patterns.

A brief review of Lambda expressions

Common design pattern transformation

The strategy pattern

Case description

Fruit stores screen apples, and users can screen apples according to the color, weight and other strategies.

  1. Defining a Policy Interface
// Encapsulates the policy for choosing Apple. Public interface ApplePredicate {// The specific algorithm is given to subclasses to implement Boolean test (Apple Apple); }Copy the code
  1. Specific screening algorithm implementation
Public class applegreencolorimplements ApplePredicate {@override public Boolean test(Apple Apple) { return "green".equals(apple.getColor()); }} / public/weight algorithm class AppleHeavyWeightPredicate implements ApplePredicate {@ Override public Boolean test (Apple Apple)  { return apple.getWeight()>150; }}Copy the code
  1. Filter based on abstract conditions
public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){ List<Apple> result = new ArrayList<>(); For (Apple: inventory) {if (p.test(Apple)){result.add(Apple); } } return result; } / / can be realized by passing in different strategies to achieve the purpose of screening fruit List < > Apple heavyApples = filterApples (inventory, new AppleHeavyWeightPredicate ()); List<Apple> greenApples = filterApples(inventory, new AppleGreenColorPredicate());Copy the code

The downside of this approach is that each policy requires the definition of a concrete implementation class to implement an algorithm, leading to the addition of many classes. 4. Use anonymous class optimization

List<Apple> redApples = filterApples(inventory, new ApplePredicate() { @Override public boolean test(Apple apple){ return "green".equals(apple.getColor()); }});Copy the code

After the improvement

  1. Using Lambda expression modification, the code is obviously much cleaner than before modification
List<Apple>result= filterApples(inventory, (Apple apple)-> "red".equals(apple.getColor()));
Copy the code
  1. Use functional interfaces + generics to filter fruits that support other types
 public static<T> List<T> filter(List<T> list,Predicate<T> p){
        List<T> result =new ArrayList<>();
        for (T e : list) {
            if (p.test(e)){
                result.add(e);
            }
        }
        return result;
    }
Copy the code

Note: the Predicate interface is a functional interface provided by Java8 and can be used without definition

Template method

Case description

Imagine a simple online shop system with an operation process: the customer first enters the account number, the system queries the account information, and finally completes some user-related marketing operations. Different branches have different marketing methods, such as giving out red envelopes and pushing advertisements. This simple functionality can be implemented in the form of an abstract class.

Public abstract Class OnlineShop {public void processCustomer(String Account){// Query Customer C by account =DBUtils.getCustomerByAccount(account); // promotionForCustomer(c); Void promotionForCustomer(Customer c); }Copy the code

Considering that the method promotionForCustomer returns no value, we can use the functional interface Consumer in conjunction with the Lambda expression to implement the above scenario as follows:

public void processCustomer(String account,Consumer<Customer> promotion){ Customer c =DBUtils.getCustomerByAccount(account); // Accept is the only method in the functional interface Consumer. }Copy the code

It is now easy to implant different behaviors by passing Lambda expressions, without having to inherit the OnlineShop class

New OnlineShop().processCustomer("xs",c-> system.out.println (" send a red envelope. + c.getName()));Copy the code

Observer model

Case Description There is a simple message push system, the system will push some messages irregularly, users can receive push messages after following, different followers have different message processing methods. The implementation idea is roughly as follows:

  1. Defines an abstract observer interface for processing incoming messages
public interface Observer {
    void handler (String msg);
}
Copy the code
  1. Define an observed interface
Public interface Subject {// Register an Observer public void registerObserver(Observer o); Public void notifyObserver(String MSG); }Copy the code
  1. Observed concrete implementation, used to register observers, and push messages
public interface SubjectImpl implements Subject{ private final List<Observer> observers = new ArrayList<>(); Public void registerObserver(Observer o){this.observe.add (o); Public void notifyObserver(String MSG){observers. ForEach (o -> observers (MSG)); }}Copy the code
  1. Specific implementation of different followers
public class A implements Observer { @Override public void handler (String msg) { this.message = message; System.out.println(" A received push message: "+ message); } } public class B implements Observer { @Override public void handler (String msg) { this.message = message; System.out.println(" little B received push message: "+ message); } } public class B implements Observer { @Override public void handler (String msg) { this.message = message; System.out.println(" little C received push message: "+ message); }}Copy the code
  1. Message notification specific function realization
Subject s =new SubjectImpl(); f.registerObserver(new A()); f.registerObserver(new B()); f.registerObserver(new C()); F.notifyobserver (" This is a meaningless message ~~~");Copy the code

Use Lambda expressions to avoid defining too many observer implementation classes

Subject s =new SubjectImpl(); S.registerobserver (MSG ->{system.out.println (" A "+ message); }) s.registerObserver(MSG ->{system.out.println (" + message "); }) s.registerObserver(MSG ->{system.out.println (" small C received push: "+ message); })Copy the code

Common functional interfaces

interface The input parameters The return type instructions
UnaryOperator T T Unary function with the same input and output types
Predicate T boolean assertions
Consumer T / Consume a piece of data with only input and no output
Function<T,R> T R Input T returns R, with input and output
Supplier / T Provide a piece of data with no input but only output
BiFunction<T,U,R> (T,U) R Two input parameters
BiPredicate<L, R> (L,R) boolean Two input parameters
BiConsumer<T, U> (T,U) void Two input parameters
BinaryOperator (T,T) T A binary function of the same input and output type

conclusion

For some common design patterns, consider using Lambda expressions in conjunction with some of the functional interfaces built into Java8 to reduce some of the code bloat associated with design patterns. Again, Lambda optimization is not a good idea for all scenarios, and can be used if the program’s point-of-change execution logic is relatively simple, but not when the processing logic is more complex or has multiple points of change.

reference

<Java Field Edition 2 >