여기서 태그달린 클래스라는 건 클래스가 가지고 있는 필드 중에 일부가 그 클래스의 구체적인 타입을 나타내는 경우가 있습니다. 예를 들어 아래와 같은 Figure클래스가 있습니다.
package me.whiteship.chapter04.item23.taggedclass;
// 코드 23-1 태그 달린 클래스 - 클래스 계층구조보다 훨씬 나쁘다! (142-143쪽)
class Figure {
enum Shape { RECTANGLE, CIRCLE, SQUARE };
// 태그 필드 - 현재 모양을 나타낸다.
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) {
if (this.length == this.width) {
shape = Shape.SQUARE;
} else {
shape = Shape.RECTANGLE;
}
this.length = length;
this.width = width;
}
double area() {
switch(shape) {
case RECTANGLE, SQUARE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError(shape);
}
}
}
Figure 클래스를 보면 Shape을 enum타입으로 RECTANGLE, CIRCLE, SQUARE중 한가지를 나타낼 수 있도록 구현이 되어 있습니다. 이렇게 Shape이라는 태그가 달려있는 클래스입니다. 이러한 클래스에는 여러가지 단점이 존재합니다. 예를 들어 RECTANGLE에 필요한 필드들이 있고, CIRCLE에 필요한 필드들이나 메소드가 있을텐데 그런것들이 모두 한 클래스에 정의되어 있습니다. 그래서 RECTANGLE관점에서는 CIRCLE과 관련있는 radius필드가 필요없고 CIRCLE관점에서는 RECTANGLE과 관련있는 length, width필드가 필요없습니다. 이렇게 쓸때 없는코드가 한 곳에 모이게 됩니다. 또한 Shape마다 필요한 기능들이 모여있어 가독성도 나빠집니다. 메모리 관점에서 보면 인스턴스 생성시 Figure에 모든 필드가 다 만들어지면서 상관없는 메모리까지 차지하게됩니다. 만약 필드를 final로 선언하고 싶다면 필요없는 필드까지 초기화를 시켜줘야 합니다. 그리고 Figure의 인스턴스를 생성해서 쓸 때 이 Figure가 RECTANGLE을 나타내는지 CIRCLE인지 SQUARE인지 단순하게 인스턴스의 타입만 보고 알 수 없습니다. 그래서 이런 문제점을 해결하기 위해서 상속을 사용하면 깔끔하게 해결할 수 있습니다.
package me.whiteship.chapter04.item23.hierarchy;
// 코드 23-2 태그 달린 클래스를 클래스 계층구조로 변환 (144쪽)
abstract class Figure {
abstract double area();
}
위와 같이 Figure 추상클래스에서는 태그값과 상관없는 모든 Figure에서 공통적으로 쓰이는 추상메서드를 선언하고 각각의 하위 클래스를 생성합니다.
package me.whiteship.chapter04.item23.hierarchy;
// 코드 23-2 태그 달린 클래스를 클래스 계층구조로 변환 (144쪽)
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; }
}
package me.whiteship.chapter04.item23.hierarchy;
// 코드 23-2 태그 달린 클래스를 클래스 계층구조로 변환 (144쪽)
class Circle extends Figure {
final double radius;
Circle(double radius) { this.radius = radius; }
@Override double area() { return Math.PI * (radius * radius); }
}
package me.whiteship.chapter04.item23.hierarchy;
// 태그 달린 클래스를 클래스 계층구조로 변환 (145쪽)
class Square extends Rectangle {
Square(double side) {
super(side, side);
}
}
이렇게 각각의 모양에 필요한 필드들을 하위클래스에 정의해서 final로 초기값을 설정해서 사용할 수 있습니다. 따라서 처음에 선언했던 태그값이 포함되어있는 Figure클래스의 area메서드는 swich문을 통해서 각 태그마다 area를 계산하도록 했지만 상속구조로 바뀐 후 이런 switch문을 제거할 수 있게 되었습니다. 클래스에서 분기문이 많다면 이것을 상속으로 변환해야하는지 한번 고려해 봐야합니다.
'개발관련 서적 정리 > Effective Java' 카테고리의 다른 글
아이템 25. 톱 레벨 클래스는 한 파일에 하나만 담으라 (0) | 2023.02.05 |
---|---|
아이템 24. 멤버 클래스는 되도록 static으로 만들라 (0) | 2023.02.05 |
아이템 22. 인터페이스는 타입을 정의하는 용도로만 사용하라 (0) | 2023.02.05 |
아이템 20. 추상클래스보다 인터페이스를 우선하라 (0) | 2023.02.05 |
아이템 18. 상속보다는 컴포지션을 사용하라 (1) | 2023.02.05 |