태그 달린 클래스
태그 값을 통해 현재 표현하는 의미가 무엇인지 결정하는 클래스 형태가 있다. 아래는 그 예이다.
class Figure {
enum Shape {RECTANGLE, CIRCLE}
// 태그 필드 - 현재 모양을 나타낸다.
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);
}
}
}
이같은 태그 달린 클래스는 장점이랄게 크게 없고 단점만 가득한 형태이다. 저자는 이러한 클래스를 '장황하고, 오류를 내기 쉽고, 비효율적이다.' 라고 요약한다.
단점
1. 여러 구현이 한 클래스에 혼합되었기 때문에 가독성이 나쁘다.
2. 현재 타입에 필요하지 않은 구현도 함께 존재하기 때문에 메모리를 많이 사용한다.
3. 필드를 final로 선언하려면 현재 타입에 필요하지 않은 필드도 초기화해서 사용해야 한다.
- 엉뚱한 필드를 초기화해도 컴파일러에서 잡히지 않고 런타임에서 문제가 드러난다.
4. 새로운 타입을 추가할 때마다 기존 코드(switch문 등)의 수정이 필요하다.
5. 태그(shape)를 확인하지 않고서는 현재 인스턴스가 나타내는 의미가 무엇인지 알 수 없다.
클래스 계층구조
위같은 태그 달린 클래스는 클래스 계층구조를 어설프게 흉내낸 것에 불과하다. 따라서 계층구조 형태로 변경해야 하는데, 그 프로세스는 아래와 같다.
1. 계층구조의 root가 될 추상 클래스를 정의한다.
2. 태그(타입) 값에 따라 동작이 달라지는 메서드를 루트 클래스의 추상 메서드로 선언한다.
3. 태그 값에 상관없이 동작이 일정한 메서드는 루트 클래스에 일반 메서드로 추가한다.
- 위의 Figure 에선 area() 가 그 예이다.
4. 공통으로 사용하는 모든 데이터 필드를 추상 클래스에 선언한다.
5. 태그별로 구체 클래스를 구현한다.
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;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
double area() {
return length * width;
}
}
Figure 예시 클래스를 계층구조로 변환한 코드이다. 앞서 언급했던 대부분의 단점이 사라졌다.
장점
1. 현재 타입과 관련 없는 코드가 없다.
2. 생성자가 모든 필드를 초기화했는지, 추상 메서드를 모두 구현했는지 컴파일러가 잡아준다.
3. 기존 루트 코드를 건드리지 않고 확장할 수 있는 유연성이 있다.
정리
1. 태그 달린 클래스 대신 계층구조로 설계하도록 하자.
2. 기존 코드에 태그 필드가 적용된 형태가 있다면 계층 구조로 리팩토링하는 것을 검토해보자.
'Dev > Java' 카테고리의 다른 글
[Effective Java : Item 32] 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2024.02.22 |
---|---|
[Effective Java : Item 28] 배열보다는 리스트를 사용하라 (0) | 2024.02.18 |
[Effective Java : Item 19] 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (2) | 2024.02.12 |
[Effective Java : Item 16] public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) | 2024.02.09 |
[Effective Java : Item 12] toString을 항상 재정의하라 (0) | 2024.02.04 |