The second Edition of Effective Java, Third Edition was published in 2009, nearly eight years ago, but with Java 6,7,8, Even with the release of 9, the Java language has undergone profound changes. Translated into Chinese here for the first time. For everyone to learn and share. Book source code address: github.com/jbloch/effe… Note that some of the code in the book contains methods based on the Java 9 API, so it is best to download JDK versions later than JDK 9. But Java 9 is only an interim release, so installing JDK 10 is recommended.


Effective Java, Third Edition

23. Use class hierarchies in preference to tag classes

Sometimes you will come across a class whose instance has two or more styles and contains a tag field that indicates the style of the instance. For example, consider this class, which can represent a circle or rectangle:

// Tagged class - vastly inferior to a class hierarchy! class Figure { enum Shape { RECTANGLE, CIRCLE }; // Tag field - the shape of this figure final Shape shape; // These fields are used only if shape is RECTANGLE double length; double width; // This field is used only if shape is CIRCLE double radius; // Constructor for circle Figure(double radius) { shape = Shape.CIRCLE; this.radius = radius; } // Constructor for rectangle Figure(double length, double width) { shape = Shape.RECTANGLE; this.length = length; this.width = width; } double area() { switch(shape) { case RECTANGLE: return length * width; case CIRCLE: return Math.PI * (radius * radius); default: throw new AssertionError(shape); }}}Copy the code

Such a tag class has many disadvantages. Their messy boilerplate code includes enumeration declarations, label attributes, and switch statements. The readability is worse because multiple implementations are jumbled together in a single class. Memory usage increases because the instance burden belongs to other style-independent domains. Properties cannot be final unless the constructor initializes unrelated properties, resulting in more boilerplate code. The constructor, with the help of the compiler, must set the tag properties and initialize the correct data properties: if the wrong properties are initialized, the program will fail at run time. You cannot add it to a tagged class unless you can modify its source file. If you add a style, you must remember to add a case to each switch statement, otherwise the class will fail at run time. Finally, the data type of an instance gives no clue about the style. In short, tag classes are verbose, error-prone, and inefficient.

Fortunately, object-oriented languages like Java offer a better option for defining a single data type that can represent objects of many styles: subtyping. The tag class is just a simple imitation of a class hierarchy.

To convert a tag class to a class hierarchy, you first define an abstract class containing abstract methods whose behavior depends on the tag value. In the Figure class, there is only one such method, the Area method. This abstract class is the root of the class hierarchy. If there are any methods whose behavior does not depend on the value of the tag, put them in this class. Also, if you have data attributes that all methods use, put them in this class. There are no such type-independent methods or attributes in the Figure class.

Next, define a concrete subclass of the root class for each type of the original tag class. In our example, there are two types: circle and rectangle. Contain data fields specific to the change type in each subclass. In our example, the radius attribute is circular, and the length and width attributes are rectangular. Also include in each subclass the appropriate implementation of each abstract method in the root class. Here is the class hierarchy corresponding to the Figure class:

// Class hierarchy replacement for a tagged class abstract class Figure { abstract double area(); } class Circle extends Figure { final double radius; Circle(double radius) { this.radius = radius; } @Override double area() { return Math.PI * (radius * radius); } } class Rectangle extends Figure { final double length; final double width; Rectangle(double length, double width) { this.length = length; this.width = width; } @Override double area() { return length * width; }}Copy the code

This class hierarchy corrects every shortcoming of the label classes mentioned earlier. The code is straightforward and does not contain the boilerplate in the original text. Each type of implementation is assigned by its own classes, none of which are occupied by extraneous data attributes. All properties are final. The compiler ensures that each class’s constructor initializes its data attributes, and that each class has an implementation for each abstract method declared in the root class. This eliminates the possibility of run-time failures due to missing switch-case statements. Multiple programmers can inherit class hierarchies independently and operate with each other without having to access the source code of the root class. Each type has a separate data type associated with it, allowing the programmer to indicate the type of the variable and to restrict variables and input parameters to specific types.

Another advantage of class hierarchies is that they can be made to reflect natural hierarchies between types, which increases flexibility and makes compile-time type checking more efficient. Assume that the tag class in the original example also allows the use of squares. Class hierarchies can be used to reflect that a square is a special kind of rectangle (assuming they are immutable) :

lass Square extends Rectangle { Square(double side) { super(side, side); }}Copy the code

Note that properties in the above layer are accessed directly, not by methods. For the sake of brevity, this would be a bad design if the class hierarchy were public (item 16).

In short, tag classes are rarely applicable. If you want to write a class with obvious tag attributes, consider whether the tag attributes can be removed and the class replaced by a class hierarchy. When you encounter an existing class with a tag attribute, consider refactoring it into a class hierarchy.