태그 달린 클래스란, 하나의 클래스가 두 가지 이상의 의미를 표현 가능할 때, 그중 현재 표현하는 의미를 태그 값으로 알려주는 클래스이다. 아래 예시를 보자.
class Figure {
enum Shape { RECTANGLE, CIRCLE }; // TAG
// 태그 필드 - 현재 모양을 나타낸다.
final Shape shape;
// 다음 필드들은 모양이 사각형(RECTANGLE)일 때만 쓰인다.
double length;
double width;
// 다음 필드는 모양이 원(CIRCLE)일 때만 쓰인다.
double radius;
// 원용 생성자
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
// 사각형용 생성자
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);
}
}
}
switch
문 등 쓸데없는 코드로 가독성이 떨어진다.final
로 선언하려면 해당 의미에 쓰이지 않는 필드들까지 생성자에서 초기화해야한다.switch
문 코드를 수정해야 한다.즉, 태그 달린 클래스는 장황하고, 오류를 내기 쉽고, 비효율적이다.
이러한 문제를, 클래스 계층구조를 활용하는 서브타이핑 (subtyping)을 통해 해결할 수 있다. 클래스 계층 구조는, 추상 클래스와 추상 메서드를 통해 타입 하나로 다양한 의미의 객체를 표현할 수 있게 해준다.
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; }
}
final
로 선언해 불변을 보장할 수 있다.