This is the second day of my participation in Gwen Challenge


New language features often overshadow existing programming patterns or designs. For example, the for-each loop introduced in Java 5 has replaced many explicit iterators because of its robustness and brevity. The introduction of the diamond operator (<>) in Java7, which eliminates the need to explicitly use generics when creating instances, has helped push Java programmers to adopt type interfaces.

The summary of design experience is called design pattern. When designing software, you can reuse these methods to solve common problems if you wish. This looks like the way traditional building engineers work, defining reusable solutions for typical scenarios. For example, the visitor pattern is often used to separate a program’s algorithm from its manipulation objects. The singleton pattern is typically used to limit the instantiation of a class to only one copy of the object.

Lambda expressions add another powerful tool to the programmer’s toolbox. They provide new solutions to the problems faced by traditional design patterns, and they are often more efficient and simpler to adopt. With Lambda expressions, many of the existing slightly bloated object-oriented design patterns can be implemented in a more streamlined manner.

This article takes a look at how using java8 features can take a different approach to the problems that design patterns were originally intended to solve.

1. Strategy mode

The policy pattern represents a generic solution to a class of algorithms, and you can choose which one to use at run time. This pattern can be applied to a wider range of areas, such as using different standards to validate input and different ways to analyze or format input.

The policy pattern consists of three parts:

  • An interface that represents an algorithm (which is the interface of the policy pattern).
  • One or more concrete implementations of the interface that represent multiple implementations of the algorithm.
  • One or more clients that use policy objects.

Suppose you want to verify that your input is properly formatted according to standards (such as containing only lowercase letters or numbers).

1, 1 traditional way

We can start by defining an interface that validates text (represented as a String) :

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

Second, define one or more concrete implementations of the interface:

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

After that, you can use these slightly different validation strategies in your program:

public class Validator{
	private final ValidationStrategy strategy;
	public Validator(ValidationStrategy v){
		this.strategy = v;
	}
	public boolean validate(String s){
		return strategy.execute(s);
	}
}

Validator numericValidator = new Validator(new IsNumeric());
boolean b1 = numericValidator.validate("aaaa");
Validator lowerCaseValidator = new Validator(new IsAllLowerCase ());
boolean b2 = lowerCaseValidator.validate("bbbb");
Copy the code

1. 2 JAVA8

Using java8, you can see that ValidationStrategy is a function interface and has the same function description as Predicate. This means that instead of declaring a new class to implement a different policy, we can achieve the same goal by passing Lambda expressions directly, but more succinct:

Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
boolean b1 = numericValidator.validate("aaaa");

Validator lowerCaseValidator =new Validator((String s) -> s.matches("\\d+"));
boolean b2 = lowerCaseValidator.validate("bbbb");
Copy the code

As mentioned above, Lambda expressions avoid the rigid template code that comes with the policy design pattern. Lambda expressions actually encapsulate part of the code (or policy), which is why the policy design pattern was created. Therefore, it is strongly recommended that similar problems be solved using Lambda expressions as much as possible.

2. Template design mode

If you need to adopt the framework of an algorithm and want to have some flexibility to improve some parts of it, then using template method is a more common scheme. The template method pattern is useful when you want to use this algorithm, but you need to modify some of the rows to get the desired effect.

Suppose you need to write a simple online banking application. Typically, a user needs to enter a user account, and then the application can get the user’s details from the bank’s database and finally do something satisfactory to the user. Online banking apps in different branches may also keep customers happy in slightly different ways, such as paying a bonus to their account or simply sending out fewer promotional documents. The following abstract class is used to implement the online banking application:

2, 1 Traditional way

public abstract class OnlineBanking {

	public void processCustomer(int id){
		Customer c = Database.getCustomerWithId(id);
		makeCustomerHappy(c);
	}
	
	abstract void makeCustomerHappy(Customer c);
}
Copy the code

The processCustomer method frames the online banking algorithm: it takes the ID provided by the customer and provides the service to satisfy the customer. Different branches can provide differentiated implementations of this method by inheriting the OnlineBanking class.

2. JAVA8

Lambda expressions can also be used to solve these problems (creating an algorithm framework that lets specific implementations plug in parts). The different algorithm components that you want to insert can be implemented as Lambda expressions or method references. Here we introduce a second parameter to the processCustomer method, which is of type Consumer, consistent with the makeCustomerHappy characteristics defined earlier:

public void processCustomer(int id, Consumer<Customer> makeCustomerHappy){
	Customer c = Database.getCustomerWithId(id);
	makeCustomerHappy.accept(c);
}
Copy the code

It is now very easy to insert different behaviors directly by passing Lambda expressions, without having to inherit from the OnlineBanking class:

new OnlineBankingLambda().processCustomer(1337, (Customer c) ->System.out.println("Hello " + c.getName());
Copy the code

This is yet another example of how Lamba expressions address the inherent design rigidities of design patterns.

3. Observer mode

This scheme is used when an object (often called a topic) needs to automatically notify multiple other objects (called observers) when certain events occur (such as state transitions). This design pattern is often used when creating graphical user interface (GUI) programs. In this case, a series of observers are registered on a graphical user interface component such as a button. If the button is clicked, the observer is notified and then performs a specific action. But the Observer mode is not limited to graphical user interfaces. For example, the observer design pattern also applies to stock trading situations, where multiple brokers may want to respond to a movement in the price of a particular stock (subject).

Suppose you need to design and implement a custom notification system for an application like Twitter. Several newspapers, such as the New York Times, the Guardian and Le Monde, subscribe to stories that they expect to receive special notifications when they contain keywords of interest.

3, 1 Traditional way

First, you need an observer interface, which aggregates different observers together. It has only one method called notify, which is called as soon as a new news item is received:

public interface Observer {
	void notify(String tweet);
}
Copy the code

Declare different observers (for example, here are three different newspaper organizations) and define different behaviors based on different keywords in the news:

class NYTimes implements Observer{ public void notify(String tweet) { if(tweet ! = null && tweet.contains("money")){ System.out.println("Breaking news in NY! " + tweet); } } } class Guardian implements Observer{ public void notify(String tweet) { if(tweet ! = null && tweet.contains("queen")){ System.out.println("Yet another news in London... " + tweet); } } } class LeMonde implements Observer{ public void notify(String tweet) { if(tweet ! = null && tweet.contains("wine")){ System.out.println("Today cheese, wine and news! " + tweet); }}}Copy the code

Define a Subject interface:

interface Subject{
	void registerObserver(Observer o);
	void notifyObservers(String tweet);
}
Copy the code

Subject can register a new observer using the registerObserver method, and notifyObservers of a news arrival to its observers using notifyObservers. Let’s go a step further and implement the Feed class:

class Feed implements Subject{ private final List<Observer> observers = new ArrayList<>(); public void registerObserver(Observer o) { this.observers.add(o); } public void notifyObservers(String tweet) { observers.forEach(o -> o.notify(tweet)); }}Copy the code

The Feed class internally maintains a list of observers that notify a news item when it arrives:

Feed f = new Feed(); f.registerObserver(new NYTimes()); f.registerObserver(new Guardian()); f.registerObserver(new LeMonde()); f.notifyObservers("The queen said her favourite person is Steven!" );Copy the code

The Guardian will pick up the story!

3. 2 JAVA8

All implementation classes of the Observer interface provide a method: notify. When the news arrives, they are all just executing on the same piece of code encapsulation. Lambda expressions are designed to eliminate such rigid code. Using Lambda expressions, instead of explicitly instantiating the three observer objects, you can simply pass the Lambda expression to indicate the behavior that needs to be performed:

f.registerObserver((String tweet) -> {
	if(tweet != null && tweet.contains("money")){
		System.out.println("Breaking news in NY! " + tweet);
	}
});

f.registerObserver((String tweet) -> {
	if(tweet != null && tweet.contains("queen")){
		System.out.println("Yet another news in London... " + tweet);
	}
});
Copy the code

So, can Lambda expressions be used anywhere and anytime? The answer is no! In the previous example, Lambda fits well because the actions that need to be performed are so simple that it’s easy to eliminate rigid code. However, the logic of the observer can be complex, holding state, defining multiple methods, and so on. In these cases, you should stick with the class approach.

4. Responsibility chain mode

The chain of responsibility pattern is a general scheme for creating sequences of processing objects, such as sequences of operations. One processing object may need to do some work and pass the result to another object, which then does some work and passes it on to the next processing object, and so on. Typically, this pattern is implemented by defining an abstract class that represents the processing object, in which a field is defined to record subsequent objects. Once the object completes its work, the processing object passes its work to its descendants.

4, 1 Traditional way

public abstract class ProcessingObject<T> { protected ProcessingObject<T> successor; public void setSuccessor(ProcessingObject<T> successor){ this.successor = successor; } public T handle(T input){ T r = handleWork(input); if(successor ! = null){ return successor.handle(r); } return r; } abstract protected T handleWork(T input); }Copy the code

Create two processing objects that do some text processing:

public class HeaderTextProcessing extends ProcessingObject<String> {
	public String handleWork(String text){
		return "There is a person: " + text;
	}
}

public class SpellCheckerProcessing extends ProcessingObject<String> {
	public String handleWork(String text){
		return text.replaceAll("Steven", "niu");
	}
}
Copy the code

Now you can combine these two processing objects to construct a sequence of operations!

ProcessingObject<String> p1 = new HeaderTextProcessing();
ProcessingObject<String> p2 = new SpellCheckerProcessing();
p1.setSuccessor(p2);

String result = p1.handle("Steven is niubility persion.");
Copy the code

4. 2Java8

This pattern looks like it is chaining (that is, constructing) functions, and you can treat the processing object as an instance of the function, or more specifically, as an instance of UnaryOperator-. To link these functions, they need to be constructed using the andThen method.

UnaryOperator<String> headerProcessing = (String text) -> "There is a persion: " + text;
UnaryOperator<String> spellCheckerProcessing = (String text) -> text.replaceAll("Steven", "niu");
Function<String, String> pipeline = headerProcessing.andThen(spellCheckerProcessing);

String result = pipeline.apply("Steven is niubility persion");
Copy the code

5. Factory mode

With the factory pattern, objects can be created without exposing the instantiation logic to the customer. Working for a bank, say, they need a way to create different financial products: loans, options, stocks, and so on. Typically, you create a factory class that contains a method responsible for implementing different objects

5, 1 Traditional way

public class ProductFactory { public static Product createProduct(String name){ switch(name){ case "loan": return new Loan(); case "stock": return new Stock(); case "bond": return new Bond(); default: throw new RuntimeException("No such product " + name); }}}Copy the code

Here loans, stocks and bonds are subcategories of products. The createProduct method sets each created product with additional logic. However, there are obvious benefits to creating objects without having to worry about exposing constructors or configurations to customers, which makes it much easier for customers to create products:

Product p = ProductFactory.createProduct("loan");
Copy the code

5. 2Java8 mode

After java8, constructors can be referenced as methods. For example, here is an example that references the loan constructor:

Supplier<Product> loanSupplier = Loan::new;
Loan loan = loanSupplier.get();
Copy the code

In this way, you can refactor the previous code to create a Map that maps the product name to the corresponding constructor:

final static Map<String, Supplier<Product>> map = new HashMap<>();
static {
	map.put("loan", Loan::new);
	map.put("stock", Stock::new);
	map.put("bond", Bond::new);
}
Copy the code

You can now use this Map to instantiate different products as you did earlier with the factory design pattern.

public static Product createProduct(String name){ Supplier<Product> p = map.get(name); if(p ! = null){ return p.get(); } throw new IllegalArgumentException("No such product " + name); }Copy the code

6, summary

Lambda expressions will help avoid using object-oriented design patterns, it’s easy to have a rigid template code, functional programming the concrete practice of declarative programming (” the need to use not only the expressions of the mutual influence, describe what want to do, by the system to select how to implement “) and has no side effects, these two ideas can help to build and maintain the system more easily.