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.
- 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
- 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
- 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
- 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
- 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:
- Defines an abstract observer interface for processing incoming messages
public interface Observer {
void handler (String msg);
}
Copy the code
- Define an observed interface
Public interface Subject {// Register an Observer public void registerObserver(Observer o); Public void notifyObserver(String MSG); }Copy the code
- 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
- 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
- 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 >