-
- Sequence: what is a static factory method
-
- Effective Java
- 2.1 the first advantage of static factory methods different from constructors is that they have names
- 2.2 the second advantage is that you don't have to create a new object every time it is called
- 2.3 the third advantage is that you can return subclasses of the original return type
- 2.4 the fourth advantage is that it can make the code concise when creating instances with generics
-
- besides
3.1 there can be multiple factory methods with the same parameters but different names
3.2 attributes that can reduce external exposure
3.3 a layer of control is added to facilitate unified modification
- besides
-
- summary
1. Sequence: what is a static factory method
In Java, the easiest way to get a class instance is to use new Keyword to create an object through a constructor.
Like this:
Fragment fragment = new MyFragment(); // or Date date = new Date();
However, in actual development, we often see another method to obtain class instances:
Fragment fragment = MyFragment.newIntance(); // or Calendar calendar = Calendar.getInstance(); // or Integer number = Integer.valueOf("3");
↑ like this: fail new, but a static method to provide its own instance externally, that is, the so-called static factory method.
Knowledge points: new What did you do?
To put it simply: when we use new to construct a new class instance, we actually tell the JVM that I need a new instance. JVM automatically opens up a space in memory, then calls the constructor to initialize member variables, and returns the reference back to the caller.
2. Effective Java
Among the books on Java, Effective Java is definitely one of the most famous. In this book, the author summarizes dozens of golden suggestions to improve Java programming. The first one at the beginning is "consider using static factory method instead of constructor". The author summarizes four reasons (Second Edition). Let's take a look one by one.
2.1 the first advantage of static factory methods different from constructors is that they have names
Due to the characteristics of the language, Java constructors are the same as class names. One problem caused by this is that the name of the constructor is not flexible enough and often can not accurately describe the return value, especially when there are multiple overloaded constructors. If the parameter types and numbers are similar, it is easy to make mistakes.
For example, the following code:
Date date0 = new Date(); Date date1 = new Date(0L); Date date2 = new Date("0"); Date date3 = new Date(1,2,1); Date date4 = new Date(1,2,1,1,1); Date date5 = new Date(1,2,1,1,1,1);
——Date class has many overloaded functions. For developers, if they are not particularly familiar with it, they may need to hesitate to find a suitable constructor. For other code readers, it is estimated that they need to check the document to understand the meaning of each parameter.
(of course, in the current Java version, the Date class only retains a parameterless constructor and a parameterless constructor, and the others have been marked @ Deprecated)
If you use the static factory method, you can give the method more meaningful names, such as the previous one valueOf,newInstance,getInstance For code writing and reading can be clearer.
2.2 the second advantage is that you don't have to create a new object every time it is called
This is easy to understand. Sometimes external callers only need to get an instance and don't care whether it is a new instance; Or when we want to provide a singleton externally - if we use the factory method, it can be easily controlled internally to prevent the creation of unnecessary objects and reduce the overhead.
In the actual scenario, the writing method of singleton is mostly realized by static factory method.
If you want to know more about single cases, you can look here: ☞ Hi, let's talk about a single example of Java
2.3 the third advantage is that you can return subclasses of the original return type
Needless to say, one of the basic principles in design patterns is the "Richter substitution" principle, which means that subclasses should be able to replace parent classes.
Obviously, a constructor can only return the exact type of itself, while a static factory method can be more flexible and can easily return an instance of any of its subtypes as needed.
Class Person { public static Person getInstance(){ return new Person(); // It can be changed to return new player() / cook() } } Class Player extends Person{ } Class Cooker extends Person{ }
For example, in the above code, the static factory method of the Person class can return an instance of Person or its subclass Player or cook as needed. (of course, this is just for demonstration. In actual projects, a class should not depend on its subclasses, but if the getInstance () method here is located in other classes, it has more practical significance.)
2.4 the fourth advantage is that it can make the code concise when creating instances with generics
This is mainly for the cumbersome declaration of generic classes. You need to write generic parameters twice:
Map<String,Date> map = new HashMap<String,Date>();
However, this method has been optimized since Java 7 - when assigning a variable of a known type, the generic parameters can be derived, so the generic parameters can be omitted when creating an instance.
Map<String,Date> map = new HashMap<>();
So this problem actually no longer exists.
3. In addition
The above are the reasons for using static factory methods instead of constructors summarized in Effective Java. If you are still hesitant after reading it, I think I can give you more reasons - I personally use static factory methods in a large number of projects. From my practical experience, in the afterlife, in addition to the above summarized reasons, The static factory approach actually has more advantages.
3.1 there can be multiple factory methods with the same parameters but different names
Although there can be multiple constructors, because the function name has been fixed, it is required that the parameters must be different (type, quantity or order) before they can be overloaded.
For example:
class Child{ int age = 10; int weight = 30; public Child(int age, int weight) { this.age = age; this.weight = weight; } public Child(int age) { this.age = age; } }
The Child class has age and weight attributes. As shown in the code, it already has two constructors: Child(int age, int weight) and Child(int age). At this time, if we want to add another constructor that specifies wegiht but does not care about age, it is generally as follows:
public Child( int weight) { this.weight = weight; }
↑ but to add this constructor to the child class, we all know it will not work, because the function signature of java ignores the parameter name, so Child(int age) Follow Child(int weight) Will conflict.
At this point, the static factory method is ready.
class Child{ int age = 10; int weight = 30; public static Child newChild(int age, int weight) { Child child = new Child(); child.weight = weight; child.age = age; return child; } public static Child newChildWithWeight(int weight) { Child child = new Child(); child.weight = weight; return child; } public static Child newChildWithAge(int age) { Child child = new Child(); child.age = age; return child; } }
Among them newChildWithWeight and newChildWithAge refers to two methods with the same parameter types but different functions. In this way, it can meet the requirements similar to Child(int age) mentioned above Follow Child(int weight) simultaneous requirements.
(in addition, the names of these two functions are also self describing, which can express their own meaning better than the invariable constructor. This is also the first advantage mentioned above - "they have names")
3.2 attributes that can reduce external exposure
There is a very important experience in software development: the more attributes exposed, the more error prone the caller is. Therefore, for class providers, generally speaking, efforts should be made to reduce the exposure of attributes, so as to reduce the chance of errors by callers.
Consider the following Player class:
// Player : Version 1 class Player { public static final int TYPE_RUNNER = 1; public static final int TYPE_SWIMMER = 2; public static final int TYPE_RACER = 3; protected int type; public Player(int type) { this.type = type; } }
Player provides a constructor to let the user pass in a type to represent the type. The expected calling method of this class is as follows:
Player player1 = new Player(Player.TYPE_RUNNER); Player player2 = new Player(Player.TYPE_SWEIMMER);
However, we know that providers are unable to control the behavior of the caller.
Player player3 = new Player(0); Player player4 = new Player(-1); Player player5 = new Player(10086);
The value passed in by the constructor expected by the provider is one of several constants defined in advance, but if it is not, it is easy to cause program errors.
——To avoid this error, using enumeration instead of constant value is one of the common methods. Of course, if you don't want to use enumeration, using the protagonist static factory method we mentioned today is also a good method.
Insert:
In fact, the use of enumeration also has some disadvantages, such as increasing the cost of the caller; If the enumeration class members increase, errors will occur in some calling scenarios that need to completely cover all enumerations.
If the above requirements are implemented by the static factory method, the code is roughly as follows:
// Player : Version 2 class Player { public static final int TYPE_RUNNER = 1; public static final int TYPE_SWIMMER = 2; public static final int TYPE_RACER = 3; int type; private Player(int type) { this.type = type; } public static Player newRunner() { return new Player(TYPE_RUNNER); } public static Player newSwimmer() { return new Player(TYPE_SWIMMER); } public static Player newRacer() { return new Player(TYPE_RACER); } }
Note that the constructor is declared for private, which can prevent it from being called externally. Therefore, when using the Player instance, the caller must basically create it through static factory methods such as newRunner, newSwimmer and newRacer. The caller does not need to know or specify the type value - in this way, the range of type assignment can be controlled to prevent the above-mentioned abnormal value.
Insert:
To be more rigorous, reflection can still bypass the static factory method, directly call the constructor, and even directly modify the type value of a created Player instance. However, this article will not discuss this unconventional situation for the time being.
3.3 a layer of control is added to facilitate unified modification
We must have encountered this scenario many times in development: when writing an interface, the data on the server is not ready. At this time, we often need to write a test data on the client to test the interface, such as:
// Create a test data User tester = new User(); tester.setName("Lao Zhang next door"); tester.setAge(16); tester.setDescription("I live next door. My name is Zhang!"); // use tester bindUI(tester); ......
To write a series of test code, if there are multiple interfaces to be tested, the series of code may be copied to multiple locations of the project.
The disadvantages of this writing method are, first of all, the code is bloated and chaotic; Secondly, if you miss something and forget to modify it when you go online, it can be said to be a disaster
However, if you, like me, are used to using static factory methods instead of constructors, you will write this naturally. First define a newTestInstance method in the User:
static class User{ String name ; int age ; String description; public static User newTestInstance() { User tester = new User(); tester.setName("Lao Zhang next door"); tester.setAge(16); tester.setDescription("I live next door. My name is Zhang!"); return tester; } }
Then the place to call can be written in this way:
// Create a test data User tester = User.newTestInstance(); // use tester bindUI(tester);
Does it feel a lot more elegant in an instant?!
Moreover, the code is not only concise and elegant. Since all test instances are created in this place, when formal data is required, you only need to delete or modify this method at will. All callers will fail to compile, completely eliminating the situation that there are test codes on the line due to negligence.
4. Summary
Generally speaking, I think "consider using static factory methods instead of constructors". In addition to the grammatical advantages of names and subclasses, it has more engineering significance. I think its main function is to increase the control of class providers over the classes they provide.
As a developer, when we, as callers, use the class provided by others, if we want to use the new keyword to create a class instance for it, we must be very careful if we are not particularly familiar with the class—— new It is so easy to use that it is often abused. New anytime and anywhere is a great risk. In addition to performance and memory problems, it often makes the code structure chaotic.
When we are the provider of a class, we cannot control the specific behavior of the caller, but we can try to use some methods to increase our control over the class and reduce the chance of the caller making mistakes, which is also a concrete embodiment of being more responsible for the code.
5. Finally
——Thank you for spending a lot of time here. I hope the content of the article doesn't make you feel wasted