A project cannot be without tool classes. The original intention of tool classes is good and code reuse, but later, tool classes become more and more chaotic. There are dozens of tool classes in some projects, which are dazzling and repetitive. I have some suggestions on how to write good tool classes:
Hidden implementation
It is to define your own tool classes and try not to call third-party tool classes directly in business code. This is also an embodiment of decoupling. If we do not define our own tool classes but directly use third-party tool classes, there are two disadvantages:
- Different people will use different third-party tool libraries, which will be messy.
- It will be painful to modify the implementation logic of tool classes in the future.
Take the simplest empty string judgment as an example. Many tool libraries have StringUtils tool classes. If we use the commons tool class, we directly use StringUtils.isEmpty at the beginning. When the string is empty or empty, it will return true. Later, the business needs to be changed to return true if it is all blank. What should we do? We can use StringUtils.isBlank instead. It looks simple, doesn't it? If you have dozens of files called, we have to change dozens of files, isn't it a little disgusting? Later, I found that not only English spaces, but also full angle spaces should be returned as true. What should I do? The method on StringUtils can no longer meet our needs. It's really hard to change...
So my suggestion is to define a StringUtil of your own project at the beginning. If you don't want to write your own implementation, you can directly call the commons method, as follows:
public static boolean isEmpty(String str) { return org.apache.commons.lang3.StringUtils.isEmpty(str); }
When all the following spaces return true, we just need to change isEmpty to isBlank; If true is also returned when all the following full spaces are blank, we can add our own logic. We only need to change and test one place.
Take a real example, such as copying the properties and methods of objects.
At the beginning, if we don't define tool class methods, we can use * * org.springframework.beans.BeanUtils.copyProperties(source, dest) * * as a tool class. It's just a line of code, which is no different from calling our own tool class. It looks OK, doesn't it?
With the development of business, we find that the performance or some features of this method do not meet our requirements. We need to modify the method in the Commons beautils package, org.apache.commons.beautils.beanutils.copyproperties (DeST, source). At this time, the problem comes. The first problem is that the parameter order of its method is opposite to that of the previous spring tool classes, It's very error prone! The second problem is that this method throws an exception and must be declared. This change is terrible! As a result, you find that a seemingly small change has changed dozens of files, and each change has to be tested once. The risk is not so small. It's a little broken, isn't it?
After you finish the test, what should you do when you need to change it to copy parameters one day? Some special fields need to be retained (such as object id) or filtered out (such as password) and not copied? I guess you're going to collapse at this time? Don't think I'm imagining it out of thin air. I'll see you for a long time. You'll always meet one day!
Therefore, we need to define our own tool class functions, which I defined at the beginning.
public void copyAttribute(Object source, Object dest) { org.springframework.beans.BeanUtils.copyProperties(source, dest); }
Later, when we need to change it to commons beats, we can change it to this way. We can turn the parameter order around and handle the exceptions. I use Lombok's sneakythlows to handle exceptions. You can also catch and throw runtime exceptions. Personal preferences.
@SneakyThrows public void copyAttribute(Object source, Object dest) { org.apache.commons.beanutils.BeanUtils.copyProperties(dest, source); }
Later, when copying attributes, we need to keep some fields or filter out some fields. We can refer to other libraries for implementation once. We can only change the price and test one file and one method, and the risk is very controllable.
Remember the requirement change in my previous post? You can think of this as a demand change, but for the same demand change, I will finish the test in an hour and go online without any risk. You may sweat and work overtime and worry about problems...
Use parent class / interface
In the final analysis, the hidden implementation above is the idea of encapsulation / decoupling, but now it is an abstract idea. If we do this well, we can write professional tool classes. This is easy to understand, but it's easy to ignore.
For example, suppose we write a function to determine whether arraylist is empty. At first, it was like this.
public static boolean isEmpty(ArrayList<?> list) { return list == null || list.size() == 0; }
At this time, we need to think about whether the parameter type can use the parent class. We see that we only use the size method. We can know that the size method is available on the list interface, so we modified it to this.
public static boolean isEmpty(List<?> list) { return list == null || list.size() == 0; }
Later, it is found that the size method is also available on the parent class / interface Collection of list, so we can modify it to this finally.
public static boolean isEmpty(Collection<?> list) { return list == null || list.size() == 0; }
At this point, the Collection has no parent class / interface, has a size method, and the modification is over. Finally, we need to change the parameter name and stop using list. After modification, all Collection objects can be used. The final version is as follows:
public static boolean isEmpty(Collection<?> collection) { return collection == null || collection.size() == 0; }
Does it look more versatile and more professional? The above string related tool class methods use the same idea. Finally, we modify the parameter class type from string to CharSequence and the parameter name str to cs. As follows:
public static boolean isEmpty(CharSequence cs) { return org.apache.commons.lang3.StringUtils.isEmpty(cs); }
The idea and method are very simple, but the effect is very good, and the tools written out are also very professional! To sum up, the idea is an abstract idea, mainly to modify the parameter type. The method is to find the parent class / interface up until the top is found. Remember to modify the parameter name.
Writing derived function groups using overloads
Developed brothers know that there are some tool libraries and a pile of overloaded functions, which are very convenient to call. They can often be called directly without parameter conversion. How are these written? We give examples.
Now you need to write a method to input the file name of a utf-8 file and output the contents to a list. When we first started writing, it was like this
[](javascript:void(0)😉
public static List<String> readFile2List(String filename) throws IOException { List<String> list = new ArrayList<String>(); File file = new File(filename); FileInputStream fileInputStream = new FileInputStream(file); BufferedReader br = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8")); // XXX operation return list; }
[](javascript:void(0)😉
We implement it first. After implementation, we make the first modification. Obviously, utf-8 format is likely to be changed, so we extract it as a parameter first. As soon as the method is split into two, it becomes like this.
[](javascript:void(0)😉
public static List<String> readFile2List(String filename) throws IOException { return readFile2List(filename, "UTF-8"); } public static List<String> readFile2List(String filename, String charset) throws IOException { List<String> list = new ArrayList<String>(); File file = new File(filename); FileInputStream fileInputStream = new FileInputStream(file); BufferedReader br = new BufferedReader(new InputStreamReader(fileInputStream, charset)); // XXX operation return list; }
[](javascript:void(0)😉
There is one more method. You can directly call the previous method body. There is only one copy of the main code. There is no need to make any modification to the previous call! It can be modified with confidence.
Then let's look at the implementation. In the following two lines of code, the filename of String type will be changed to File type, and then used after changing to FileInputStream type.
File file = new File(filename);
FileInputStream fileInputStream = new FileInputStream(file);
Here, we should think that the user may directly pass in the File type or the FileInputStream type. We should all need support instead of the user's own type processing! In combination with the use of the parent class in the previous point, change FileInputStream into the parent class InputStream. Our final method group is as follows:
[](javascript:void(0)😉
package plm.common.utils; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import org.apache.commons.io.IOUtils; /** * Tool class writing examples, using overloading to write function groups with different parameter types * * @author Xiao Fengqing https://github.com/xwjie/PLMCodeTemplate * */ public class FileUtil { private static final String DEFAULT_CHARSET = "UTF-8"; public static List<String> readFile2List(String filename) throws IOException { return readFile2List(filename, DEFAULT_CHARSET); } public static List<String> readFile2List(String filename, String charset) throws IOException { FileInputStream fileInputStream = new FileInputStream(filename); return readFile2List(fileInputStream, charset); } public static List<String> readFile2List(File file) throws IOException { return readFile2List(file, DEFAULT_CHARSET); } public static List<String> readFile2List(File file, String charset) throws IOException { FileInputStream fileInputStream = new FileInputStream(file); return readFile2List(fileInputStream, charset); } public static List<String> readFile2List(InputStream fileInputStream) throws IOException { return readFile2List(fileInputStream, DEFAULT_CHARSET); } public static List<String> readFile2List(InputStream inputStream, String charset) throws IOException { List<String> list = new ArrayList<String>(); BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(inputStream, charset)); String s = null; while ((s = br.readLine()) != null) { list.add(s); } } finally { IOUtils.closeQuietly(br); } return list; } }
[](javascript:void(0)😉
What about? Six methods. In fact, there is only one code body, but it provides various types of input parameters, which is very convenient to call. When the development team leader writes, it takes a little more time to write code that looks professional and easy to call. If the development team leader doesn't write it well, the developer finds that the existing method can only pass String. What she wants to pass is InputStream. She doesn't dare to change the original code, so she will copy one copy and modify it, resulting in one more duplicate code. That's how the code sucks...
The key point is to think more and write various types of input parameter functions according to parameter changes. It is necessary to ensure that there is only one copy of the main code of the function.
Use static import
A problem with tool classes is that they are easy to flood. The main reason is that developers can't find the methods they want to use, so they write one by themselves. It's difficult for developers to remember the class name, and you can't review the code every day.
Therefore, to make it easy for developers to find, we can use static import in Eclipse as follows:
Physically independent storage
This is my habit. I am used to putting code irrelevant to business into independent projects or directories. Physically, it should be separated and maintained by special personnel** Not everyone has the ability to write tool classes, store them independently, and maintain them. Special permission control helps to ensure the purity and quality of the code** In this way, ordinary developers will not modify it at will.
For example, mine Example project In it, a source directory is specially established to store framework code, and tool classes are also in it. I am the only one who will modify the code here:
summary
Almost everyone knows that object-oriented ideas have abstract encapsulation, but a few people can really do it. In fact, if they have a heart, they can reflect these ideas everywhere. When writing tool classes, you need to pay attention to parameter optimization. In large projects, do not directly call third-party tool classes in business code. Then, think more and take more steps, and consider various types of input parameters, so that you can also write professional and flexible tool classes!