Writing Quality Code: 151 Suggestions for Improving Java Programs (Chapter 6: Enumerations and Annotations _ Recommendations 88-92)

I've been studying Java annotations and Java 8's new lambda expressions in the last few days, but it's still hot, haha.

Recommendation 88: Implementing factory method patterns with enumeration is more concise

The factory method pattern is "creating an object's interface, letting subclasses decide which class to instantiate, and delaying the instantiation of one class to other subclasses". The factory method pattern is often used in our development. Take automobile manufacturing as an example to see how the General Factory Method pattern is realized.

//Abstract products
interface Car{
    
}
//Specific product categories
class FordCar implements Car{
    
}
//Specific product categories
class BuickCar implements Car{
    
}
//Factory Category
class CarFactory{
    //produce ears
    public static Car createCar(Class<? extends Car> c){
        try {
            return c.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}

This is the original factory method model. There are two products: Ford and Buick, which are then manufactured through the factory method model. With the factory method model, we don't need to care about how a car is generated. Just tell the factory to "produce a Ford for me". Here is the client code when a Ford car is produced.

public static void main(String[] args) {
    //Production vehicles
    Car car = CarFactory.createCar(FordCar.class);
}

This is the factory method mode we often use, but the frequent use does not mean that it is the best and most concise. Here is another way to implement the factory method model by enumeration. You can evaluate who is better and who is worse. There are two ways to enumerate the implementation of factory method patterns:

1. Enumerating non-static methods to implement factory method pattern

We know that each enumeration item is an instance object of the enumeration. Does that define a method that can generate the corresponding products of each enumeration item to implement this pattern? The code is as follows:

enum CarFactory {
    // Define the types of cars that can be produced
    FordCar, BuickCar;
    // produce ears
    public Car create() {
        switch (this) {
        case FordCar:
            return new FordCar();
        case BuickCar:
            return new BuickCar();
        default:
            throw new AssertionError("invalid parameter");
        }
    }

}

create is a non-static method that can only be accessed through FordCar, BuickCar enumerations. When the factory method mode is implemented in this way, it is very simple for the client to produce a car. The code is as follows:

public static void main(String[] args) {
    // Production vehicles
    Car car = CarFactory.BuickCar.create();
}

2. Generating products through abstract methods

Although the enumeration type cannot be inherited, it can modify its method with abstract, which means that the enumeration is an abstract enumeration. Each enumeration item needs to implement this method by itself. That is to say, the type of enumeration item is a subclass of the enumeration. Let's look at the code:

enum CarFactory {
    // Define the types of cars that can be produced
    FordCar{
        public Car create(){
            return new FordCar();
        }
    },
    BuickCar{
        public Car create(){
            return new BuickCar();
        }
    };
    //Abstract production method
    public abstract Car create();
}

First, an abstract manufacturing method creation is defined, and then each enumeration item is implemented by itself. This method compiles and generates an anonymous subclass of CarFactory, because each enumeration item implements the create abstract method. Client invocation is the same as the previous scheme, and it will not be further elaborated.

You may ask, why use the enumerated factory method pattern? That's because the factory method pattern using enumerated types has three advantages:

1. Avoid the occurrence of error calls: The production method in the general factory method mode can accept three types of parameters: type parameter, String parameter and int parameter. These three parameters are all broad data types, which are prone to errors (such as boundary problem, null value problem), and such error editors occur. The police will not be alerted, for example:

public static void main(String[] args) {
    // Production vehicles
    Car car = CarFactory.createCar(Car.class);
}

Car is an interface that fully meets the requirements of createCar, so it will not report any errors at compile time, but will report InstantiationException exception at run time. There is no problem with factory method mode using enumeration type. It does not need to pass any parameters, just need to choose what type of product to produce. All right.

2. Good performance, concise use: Enumeration type calculation is based on the calculation of int type, which is the most basic operation. Of course, the performance will be fast. As for the convenience of use, pay attention to the call of the client, the code literally means "automobile factory, we want a Buick car, production as soon as possible".

3. Reducing class-to-class coupling: Whether the production method receives class, string or int parameters, it becomes a burden on the client class. These classes are not required by the client, but because the limitations of the factory method must be input, such as class parameters. For the client main method, it needs to pass a fordCar.class. A Ford car can be manufactured only with parameters. In addition to passing parameters in the create method, the business class does not need to change the implementation class of car. This is a serious violation of Dickett's principle, that is, the principle of minimum knowledge: an object should have the least knowledge of other objects.

The enumerated factory method does not have this problem. It only needs to rely on the factory class to produce a car that meets the interface. It can completely ignore the existence of specific automobile class.

Recommendation 89: Limit the number of enumerated classes to 64

To make better use of enumeration, Java provides two sets of enumerations: EnumSet and EnumMap, which use relatively simple methods. EnumSet means that its elements must be enumerated items of an enumeration, EnumMap means that the Key value must be enumerated items of an enumeration, because the number of instances of enumeration type is fixed and limited. EnumSet and EnumMap are relatively more efficient than other Sets and Maps.

EnumSet of Java Collections

Note: Enumeration number should not exceed 64, otherwise split is recommended.

Recommendation 90: Carefully inherit annotations

Java introduced Annotation from version 1.5, @Inheruted, which indicates whether an annotation can be automatically inherited.

Talking about java annotations <the most easy-to-understand annotations>

Recommendation 91: The combination of enumeration and annotation is more powerful

Annotations are written in the same way as interfaces, using keyword interfaces, and can not be implemented code. Constant definitions are public static final by default. Their main difference is that annotations are added before interfaces. @ Characters And it can not be inherited and realized.

Let's illustrate with an example the list of access privileges:

interface Identifier{
    //Politeness in the absence of access
    String REFUSE_WORD  =  "You have no right to visit";
    //authentication
    public  boolean identify();
}
package OSChina.reflect;

public enum CommonIdentifier implements Identifier {
    // Permission level
    Reader, Author, Admin;
    @Override
    public boolean identify() {
        return false;
    }
}
package OSChina.reflect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Access {
    //What level can be accessed by default is administrator
    CommonIdentifier level () default CommonIdentifier.Admin;
}
package OSChina.reflect;

@Access(level=CommonIdentifier.Author)
public class Foo {
}
package OSChina.reflect;

public class Test {
    public static void main(String[] args) {
        // Initialize business logic
        Foo b = new Foo();
        // Get annotations
        Access access = b.getClass().getAnnotation(Access.class);
        // No Access comment or authentication failure
        if (null == access || !access.level().identify()) {
            // No Access comment or authentication failure
            System.out.println(access.level().REFUSE_WORD);
        }
    }
}

Looking at the above code, it's easy to understand. All developers need to add annotations to solve the access control problem.

Recommendation 92: Note the difference between @override versions

@ Override annotation is used for method overwriting. It is valid for compiler, that is, Java compiler checks whether the method is really overwritten according to the annotation when compiling, and refuses to compile if not.

 

Jiang Shuying's Reading Series Writes High Quality Codes: 151 Suggestions for Improving Java Programs @Directories

Keywords: Programming Java Lambda

Added by Undrium on Wed, 24 Jul 2019 11:54:00 +0300