GitHub 21.3 K Star Java engineers become god’s path, not to learn about it!

GitHub 21.3 K Star Java engineers become god’s path, really not to learn about it!

In daily development, we often come across a similar scenario: when something needs to be done, the steps are fixed, but the exact implementation of each step is uncertain.

In this case, we usually abstract everything we do into an abstract class and define a template method in that class. This is known as the template method pattern.

The previous template method

Here’s an example from my previous design Patterns – Template Approach to Design Patterns article:

When we go to the business hall of the bank to handle business, we need to do the following steps: 1. Get the number, 2. Evaluation.

In the three steps, the number and evaluation are fixed processes, and everyone has to do the same thing. But the business step needs to be implemented differently depending on what each person is doing.

We can encapsulate the whole business as an abstract class:

Public abstract class AbstractBusinessHandler {/** * template method */ public final void execute(){ getNumber(); handle(); judge(); } @return */ private void getNumber(){system.out.println ("number-00" + randomUtils.nextint ()); } public void handle(); /** ** private void judge(){system.out.println ("give a praise "); }}Copy the code

We define an Execute class in our class that orchestrates the getNumber, Handle, and judge methods. This is a template method.

Among them, getNumber and judge have universal implementations, while handle is abstract and needs to be rewritten by subclasses according to the actual business content to be handled.

With this abstract class and template method, when we want to implement a “money saving business”, we just need to inherit the AbstractBusinessHandeler and override the Handle method:

public class SaveMoneyHandler extends AbstractBusinessHandeler { @Override public void handle() { System.out.println("save 1000"); }}Copy the code

To execute the save business logic, just call SaveMoneyHandler’s execute method:

public static void main(String []args){
    SaveMoneyHandler saveMoneyHandler = new SaveMoneyHandler();
    saveMoneyHandler.execute();
}
Copy the code

Output result:

number-00958442164
save 1000
give a praised
Copy the code

Above, is a simple template method implementation. Using a template approach helps us reuse code to a great extent.

Since we do a lot of business in the bank, we may need to define many implementation classes:

Public class DrawMoneyHandler extends AbstractBusinessHandeler {@override public void handle() { System.out.println("draw 1000"); Public class MoneyManageHandler extends AbstractBusinessHandeler{@override public void handle() { System.out.println("money manage"); }}Copy the code

For a long time, developers have used template methods like this one: Prepare an abstract class that implements some of the logic as concrete methods and constructors, and then declare some abstract methods to let subclasses implement the rest of the logic. Different subclasses can implement these abstract methods in different ways, resulting in different implementations of the remaining logic.

However, with Java 8, template methods are implemented in a different way, without having to define as many implementation classes.

Functional programming in Java 8

In 2014, Oracle released Java 8. The biggest new feature in Java 8 was support for functional programming.

Java 8 adds a series of function interfaces under java.util.function. There are Consumer, Supplier, Predicate, Function, and so on.

This article mainly wants to introduce the Supplier and Consumer interfaces and the user interfaces, which can help us improve the template method. This is just a quick overview of their usage, not an in-depth one, but if you want to learn more, you can Google them.

Supplier

Supplier is a feeding interface that, in a nutshell, is a method that returns some value.

The simplest Supplier is this code:

public List<String> getList() {
    return new ArrayList();
}
Copy the code

Supplier refers to:

Supplier<List<String>> listSupplier = ArrayList::new;
Copy the code

Consumer

A Consumer interface, simply put, is a method that takes values (such as method parameters) and operates on them.

The simplest Consumer is this code:

public void sum(String a1) {
    System.out.println(a1);
}
Copy the code

Use Consumer to express:

Consumer<String> printConsumer = a1 -> System.out.println(a1);
Copy the code

The most common example of Consumer is stream. forEach(Consumer),

It accepts a Consumer that consumes elements in the iterating stream and performs some operations on each element, such as printing:

Consumer<String> stringConsumer = (s) -> System.out.println(s.length());
Arrays.asList("ab", "abc", "a", "abcd").stream().forEach(stringConsumer);
Copy the code

Template methods after Java 8

Now that we’ve covered Consumer and Supplier in Java 8, let’s take a look at how to modify the template methods we introduced earlier.

First, we define a BankBusinessHandler class and redefine an execute method that has an input parameter of type Consumer. Then we remove the Handle method and rearrange the template method as follows:

/** * @author Hollis */ public class BankBusinessHandler { private void execute(Consumer<BigDecimal> consumer) { getNumber(); consumer.accept(null); judge(); } private void getNumber() { System.out.println("number-00" + RandomUtils.nextInt()); } private void judge() { System.out.println("give a praised"); }}Copy the code

In the template method execute, we arranged getNumber, judge and consumer.accept, in which consumer.accept is the specific business logic, which may be saving money, withdrawing money, financing, etc. It is passed when it needs to be called execute by another method.

In this case, we need to add the following methods to the BankBusinessHandler class if we want to implement the “save money” business:

/** * @author Hollis */ public class BankBusinessHandler { public void save(BigDecimal amount) { execute(a -> System.out.println("save " + amount)); }}Copy the code

In the save method, the execute method is called and a Comsumer that implements the “save” business logic is passed in as an input parameter.

Thus, when executing the business logic of saving money, we simply call BankBusinessHandler’s save method:

public static void main(String[] args) throws {
    BankBusinessHandler businessHandler = new BankBusinessHandler();
    businessHandler.save(new BigDecimal("1000"));
}
Copy the code

Output result:

number-001736151440
save1000
give a praised
Copy the code

As mentioned above, when we want to implement the business logic of withdrawing money and managing money, it is similar to saving money:

/** * @author Hollis */ public class BankBusinessHandler { public void save(BigDecimal amount) { execute(a -> System.out.println("save " + amount)); } public void draw(BigDecimal amount) { execute(a -> System.out.println("draw " + amount)); } public void moneyManage(BigDecimal amount) { execute(a -> System.out.println("draw " + amount)); }}Copy the code

As you can see, by using Comsumer in Java 8, we have transformed the template method so that we no longer need abstract classes, abstract methods, and an implementation class for every business. We can cluster all the business logic in the same business class. This is very convenient for the later operation and maintenance of this code.

We saw how to modify the template method with Consumer. What does Supplier do?

In our example, among the three steps of number taking, business handling and evaluation, business handling needs to be customized according to business conditions. Therefore, we open business handling as an extension point to the outside world in the template method.

Have such a kind of circumstance, that is when we do business now, the way that takes number is different also, the likelihood is to take number to bank site, take number on the net or bank client manager makes an appointment need not take number to wait.

No matter how you pick a sign, you end up picking a sign; And different types of numbers, may receive different specific services, such as VIP number to VIP counter for business.

To implement this business logic, Supplier is required. Supplier is a “Supplier” that can be used to customize the “call logic”.

First, we need to modify the template method:

/** * template method */ protected void execute(Supplier<String> Supplier, Consumer<BigDecimal> consumer) { String number = supplier.get(); System.out.println(number); Println ("Assign To VIP Counter"); if (number.startswith (" VIP ")) { } else if (number.startswith ("reservation")) {// Assign reservation number To Exclusive Customer system.out.println ("Assign To Exclusive Customer Manager"); Println ("Assign To Usual Manager"); } consumer.accept(null); judge(); }Copy the code

The supplier to execute has been modified to add a supplier that can provide a number. How to fetch the number is left to the method that calls execute.

After that, we can define multiple saving methods, namely Vip saving, reservation saving and ordinary saving:

public class BankBusinessHandler extends AbstractBusinessHandler { public void saveVip(BigDecimal amount) { execute(() -> "vipNumber-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount)); } public void save(BigDecimal amount) { execute(() -> "number-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount)); } public void saveReservation(BigDecimal amount) { execute(() -> "reservationNumber-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount)); }}Copy the code

Implement different number fetching logic in different saving methods. Encapsulate the number fetching logic in the supplier and pass it to the execute method.

The test code is as follows:

BankBusinessHandler businessHandler = new BankBusinessHandler();
businessHandler.saveVip(new BigDecimal("1000"));
Copy the code

Output result:

vipNumber-001638110566
Assign To Vip Counter
save 1000
give a praised
Copy the code

Above, we have reinvented the template method pattern with Comsumer and Supplier.

Modifications to the template approach with Java 8 can further reduce the amount of code, or at least create many fewer implementation classes, greatly reducing the amount of duplicate code and improving maintainability.

Of course, this approach is not perfect, but there is a slight disadvantage, which is that the cost of understanding is slightly higher, and for developers who are not familiar with functional programming, the cost of getting started is slightly higher…

conclusion

Above, we described what the template method pattern is and how to transform the template method pattern using Comsumer and Supplier.

This approach is often used in our daily development. In fact, I think the example in this paper can not fully express the meaning I want to express, but the logic in our real business is more complicated.

So, this is a lot of understanding and practice. If you have used the template method pattern in your code, you can certainly adapt it with the methods in this article.

If you haven’t used the template method pattern, then there’s a lot of repetitive code in your application, so use it now.

As a development engineer, we should try our best to eliminate duplicate code in the application.

About the author: Hollis, a person with a unique pursuit of Coding, is a technical expert of Alibaba, co-author of “Three Courses for Programmers”, and author of a series of articles “Java Engineers becoming Gods”.

Follow the public account [Hollis], the background reply “into god map” can be downloaded to receive the Java engineer advanced mind map.