class Figure {
enum Shape { RECTANGLE, CIRCLE };
//태그 필드 - 현재 모양을 나타낸다.
final Shape shape;
//다음 필드들은 모양이 사각형(RECTANGEL)일 때만 쓰인다.
double lenght;
double width;
//다음 필드는 모양이 원(CIRCLE)일 때만 쓰인다.
double radius;
//원용 생성자
Figure(double length, double width) {
shape = Shape.RECTANGE;
this.length = length;
this.width = width;
}
double area() {
switch(shape) {
case RECTANGEL:
return length * width;
case CIRCLE:
return Math.PI * (radius*radisu);
default:
throw new AssertionError(shape);
}
}
}
필드들을 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.
-> 루트 클래스의 코드를 건드리지 않고도 다른 프로그래머들이 독립적으로 계층구조를 확장하고 함께 사용 가능.
-> 타입이 의미별로 따로 존재하나 변수의 의미를 명시하거나 제한할 수 있고,
-> 특정 의미만 매개변수로 받을 수 있음
-> 유연성과 컴파일타임 타입 검사 능력을 높여줌
태그 달린 클래스를 써야 하는 상황은 거의 없다.
새로운 클래스를 작성하는 데 태그 필드가 등장한다면
태그를 없애고 계층구조로 대체하는 방법을 생각해보자.
기존 클래스가 태그 필드를 사용하고 있다면 계층구조로 리팩터링하는 걸 고민해보자.