Chapeter 4 Classes and Interfaces
Item 20: Prefer class hierarchies to tagged classes
tagged classes are verbose, error-prone, and inefficient. Occasionally you may run across a class whose instances come in two or more flavors and contain a tag field indicating the flavor of the instance.
-
1.Tagged Classes
- [Typical Structure] cluttered with boilerplate
- including (1)enum declarations, (2)tag fields, and (3)switch statements.
-
An Example
===========================Constant Interface=========================== // 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(); } } }
- [Typical Structure] cluttered with boilerplate
-
2.shortcomings of tagged classes
- (1) Readability is further harmed
- [Reason] because multiple implementations are jumbled together in a single class.
- (2) Memory footprint is increased
- [Reason] because instances are burdened with irrelevant fields belonging to other flavors.
- (3) Fields can’t be made final
- Fields can’t be made final unless constructors initialize irrelevant fields, resulting in more boilerplate.
- (4) no help from the compiler
- Constructors must set the tag field and initialize the right data fields with no help from the compiler
- [i.e.] if you initialize the wrong fields, the program will fail at runtime.
- (5) can’t add a flavor to a tagged class unless modifying its source file
- (6) If you do add a flavor, you must remember to add a case to every switch statement
- (7) the data type of an instance gives no clue as to its flavor
- (1) Readability is further harmed
- ###3.A better alternative: class hierarchy
- object-oriented languages can offer better way to define a single data type capable of representing objects of multiple flavors
- [alternative] subtyping
- [Benefits]
- (1) Containing none of the boilerplate
- (2) The implementation of each flavor is allotted its own class
- (3) These classes are encumbered by irrelevant data fields.
- (4) All fields are final
- (5) The compiler ensures that each class’s constructor initializes its data fields
- (6) eliminates the possibility of a runtime failure due to a missing switch case.
- (7) can extend the hierarchy independently without access to the source for the root class
A tagged class is just a pallid imitation of a class hierarchy
- ###4.Method: To transform a tagged class into a class hierarchy
- (1) define an abstract class containing an abstract method for each method in the tagged class whose behavior depends on the tag value.
- 1) If there are any methods whose behavior does not depend on the value of the tag, put them in this class
- 2) if there are any data fields used by all the flavors, put them in this class.
- (2) Next, define a concrete subclass of the root class for each flavor of the original tagged class.
===================Transform a tagged class===================
// It corrects every shortcoming of tagged classes noted previously. // containing none of the boilerplate
abstract class Figure { abstract double area(); }
class Circle extends Figure { final double radius; Circle(double radius) { this.radius = radius; } 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; } double area() { return length * width; } }
class Square extends Rectangle { Square(double side) { super(side, side); } }
- (1) define an abstract class containing an abstract method for each method in the tagged class whose behavior depends on the tag value.
Note: the fields in the above hierarchy are accessed directly rather than by accessor methods. This was done for brevity and would be unacceptable if the hierarchy were public (Item 14) .
-
Conclusion
- tagged classes are seldom appropriate.