Tagged class is a class where we have multiple data types for multiple cases.
We should choose to discriminate such distinct cases by taking the advantage of abstract classes and inheritance.
Following example illustrates the point:
================================
//Tagged Class
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();
}
}
}
Demerits of the above approach:
-------------------------------
1) Memory footprint increased as instances are burdened with irrelevant fields belonging to other flavors
2) Fields can’t be made final unless constructors initialize irrelevant fields
Resolution:
-----------
// 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; }
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; }
}
=> The class hierarchy could be made to reflect the fact that a square is a special kind of rectangle (assuming both are immutable):
class Square extends Rectangle {
Square(double side) {
super(side, side);
}
}
We should choose to discriminate such distinct cases by taking the advantage of abstract classes and inheritance.
Following example illustrates the point:
================================
//Tagged Class
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();
}
}
}
Demerits of the above approach:
-------------------------------
1) Memory footprint increased as instances are burdened with irrelevant fields belonging to other flavors
2) Fields can’t be made final unless constructors initialize irrelevant fields
Resolution:
-----------
// 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; }
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; }
}
=> The class hierarchy could be made to reflect the fact that a square is a special kind of rectangle (assuming both are immutable):
class Square extends Rectangle {
Square(double side) {
super(side, side);
}
}