public class Figure {
enum Shape { RECTANGLE, CIRCLE };
final Shape shape;
double length;
double width;
double radius;
public Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
public Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
public double area() {
switch (shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError(shape);
}
}
}
다음 예시와 같이 현재 표현하는 의미를 태그로 알려주는 클래스를 가끔 볼 수 있다. shape 멤버 변수로 나타내는 것이다.
이러한 코드는 일단 문제가 많다. 열거 타입을 선언해야 하고, area 메소드의 switch 문 등의 비효율적인 코드가 들어가게 된다. 물론 이렇게 되면 가독성까지 나빠지게 된다.
shape 멤버 변수가 final 이므로 초기화를 해야되는데 이 상황에서 쓰레기 값이 들어가야 할 수도 있다.
정리하면 태그 달린 클래스는 장황하, 오류를 내기 쉽고, 클래스의 계층 구조를 흉내내다가 잘못된 케이스라고 할 수 있다.
그래서 이 태그 달린 클래스를 클래스의 계층 구조로 쪼갤 수 있는데, 먼저 공통적인 멤버 변수나 메소드가 있는지 봐야 한다. 여기서는 CIRCLE과 RECTANGLE의 공통적인 메소드는 area()밖에 없기 때문에 공통적 조상 클래스로 뺀다.
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;
}
}
클래스 계층 구조로 뽑아내게 되면 이런 식으로 작성될 수 있을 것이다. 위에서 포함되어 있는 case와 같은 불필요한 코드도 모두 빠진 모습이다. 모든 필드는 final로 작성될 수 있게 되었다. 그래서 런타임 오류가 발생할 케이스도 적어지는 것이다. 또한 정사각형과 같은 클래스를 추가하려면 Rectangle을 상속받아 아주 간단하게 모양을 추가할 수 있을 것이다.
핵심 정리
태그 달린 클래스는 계층적 클래스로 대체할 수 있다.