Equivalent to the construction method, the advantages of Java static factory method

Posted by jacobsdad on Fri, 04 Feb 2022 10:58:10 +0100

catalogue

1. Sequence: what is a static factory method

2. 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

3. In addition

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

4. Summary

This article is transferred from:

On the static factory method of Java, it's enough to read this article! Thank you very much~

1. Sequence: what is a static factory method

In Java, the easiest way to get a class instance is to use the {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");

For example, instead of "new", a static method is used to provide its own instance externally, that is, the so-called Static factory method.

Knowledge: what has new done?

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, the constructors of Java are the same as the class names. One problem caused by this is that the name of the constructor is not flexible enough, which often can not accurately describe the return value. It also often puzzles programmers about the parameters that should be filled in at each position, especially when there are multiple overloaded constructors. If the type and number of parameters are similar, it is more likely 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 static factory methods, you can give more meaningful names to the methods, such as "valueOf", "newInstance", "getInstance" and so on, which can make the code writing and reading 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 single example is mostly realized by static factory method.

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, the constructor can only return the exact type of itself, while the static factory method can be more flexible and can easily return instances 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 Cooker 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 article is mainly about 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, since the generic parameters can be deduced, 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 method instead of constructor 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 method a lot in the project. 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).


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 two attributes: age and weight. 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 like this:

public Child( int weight) {
    this.weight = weight;
}

↑ but to add this constructor to the Child class, we all know it won't work. Because the function signature of java ignores the parameter name, Child (int age) and ↑ Child (int weight) will conflict.

At this time, the static factory method can come into play.

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;
    }
}

The , newChildWithWeight , and , newChildWithAge , are two methods with the same parameter type, but with different functions. In this way, they can meet the above requirements that , Child(int age) and , Child(int weight) exist at the same time.
(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 external 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 construction method for the user to 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 call 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, it will lead to errors 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 construction method is declared as {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 has no notice and does not need to specify the type value - so that the range of type assignment can be controlled, Prevent the above-mentioned abnormal value.

Insert:
If it is more rigorous, reflection can still bypass the static factory method, directly call the constructor, or even directly modify the type value of a created Player instance, but this unconventional situation will not be discussed in this article 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 way of writing are, first of all, that the code is bloated and chaotic; Secondly, if you miss a place 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 needed, you only need to delete or modify this method at will, and all callers will fail to compile, completely eliminating the situation that there are test codes online due to negligence.

4. Summary

Generally speaking, I think "consider using static factory method instead of constructor". In addition to the grammatical advantages of name and subclass, 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 using a class provided by others as a caller, if we want to use the new keyword to create a class instance for it, if we are not particularly familiar with the class, we must be very careful - new is so easy to use that it is often abused. There is a great risk of new anytime and anywhere, except that it may lead to performance In addition to 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.

Topics: Java Back-end