핵심 정리
- 태그 달린 클래스의 단점
- 쓸데없는 코드가 많다.
- 가독성이 나쁘다.
- 메모리도 많이 사용한다.
- 필드를 final로 선언하려면 불필요한 필드까지 초기화 해야 한다.
- 인스턴스 타입만으로는 현재 나타내는 의미를 알 길이 없다.
- 클래스 계층 구조(상속)로 바꾸면 모든 단점을 해결할 수 있다.
태그가 달린 클래스는?
태그 달린 클래스는 클래스가 가진 필드 중 일부가 클래스의 구체적인 타입을 나타내고, 이 타입에 따라 클래스의 전체 동작이 바뀌는 클래스다. 예시로는 Figure 클래스를 들 수 있다.
// 코드 23-1 태그 달린 클래스 - 클래스 계층구조(상속)보다 훨씬 나쁘다! (142-143쪽)
public class Figure {
enum Shape {RECTANGLE, CIRCLE}
// 태그 필드 - 현재 모양을 나타낸다.
final Shape shape;
// 사각형일 때만 쓰인다
double length;
double width;
// 원일 때만 쓰인다.
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);
}
}
public static void main(String[] args) {
// 이 Figure는 원? 사각형?
Figure figure = new Figure(1,2);
}
}
위 태그 달린 클래스를 살펴보자.
- Figure 클래스가 존재한다.
- 내부에 Shape가 존재한다. Shape는 ENUM이고, 이 값에 따라 Figure 인스턴스가 직사각형 / 원인지를 나타낸다
많은 단점이 존재하는데 아래에서 하나씩 설명해보고자 한다.
쓸데없는 코드가 많고, 가독성이 떨어짐.
태그 달린 클래스라면 한 곳에 모이면 어색한 코드들이 모이게 된다. 이 어색한 코드들은 각 태그 관점에서 바라봤을 때 쓸모없는 코드가 된다. 또한 이런 코드가 많아지면 코드의 가독성이 떨어지게 된다. 아래를 살펴보자.
- legnth, width는 원일 때는 사용되지 않는 필드임. → 원과 같이 있기 어색함.
- radius는 사각형일 때 사용되지 않는 필드임. → 사각형과 함꼐 있기 어색함.
- area() 메서드 → 어떤 종류의 태그냐에 따라 넓이 구하는 공식이 다름. → Switch 문으로 복잡해짐.
// 사각형일 때만 쓰인다
double length;
double width;
// 원일 때만 쓰인다.
double radius;
double area() {
switch (shape) {
// 인스턴스마다 다르게 동작하는 코드
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError(shape);
}
}
이처럼 같은 곳에 있으면 어색한 코드들이 많이 모여서 코드를 읽기 어렵게 만든다. 이런 코드들은 클래스를 분리하는게 더 좋은 방향이다.
메모리를 많이 사용함.
인스턴스 종류별로 서로 다른 필드를 사용하는데 그 필드가 한 클래스에 모여있게 된다. 클래스에 필드가 존재하는 것만으로 메모리가 사용된다. 만약 그 필드들이 final로 선언되어있다면 인스턴스가 생성될 때 항상 초기화 되어야 한다. 예를 들어 원을 생성하는데 필요없는 length, width 값을 모두 가져야한다. 이처럼 메모리 낭비도 문제가 된다.
// 사각형일 때만 쓰인다
double length;
double width;
// 원일 때만 쓰인다.
double radius;
타입만으로 어떤 종류인지 알 수 없음.
클래스 타입만으로 어떤 종류인지 알 수 없기 때문에 의미가 명확하지 않게 된다. 이 클래스의 동작은 Figure라는 타입이 아니라 내부 태그인 Shape가 어떤 값이냐에 따라 서로 다른 의미를 가지고 동작하기 때문이다.
public static void main(String[] args) {
// 이 Figure는 원? 사각형?
Figure figure = new Figure(1,2);
}
태그 달린 클래스를 해결하는 방법은? → 상속
태그 달린 클래스는 상속을 사용해서 깔끔하게 해결할 수 있다. 위에서는 Figure 클래스에 Rectangle, Circle을 태그로 가지며, 태그에 따라 동작했었다. 상속으로 표현한다면 Figure를 Abstract 클래스로 생성하고, Rectangle / Circle이 이 클래스를 구현하면 된다. 이 때 Figure 추상 클래스에서 공통 메서드인 area()를 제공해주면 된다.
// 부모 클래스
abstract class Figure {
abstract double area();
}
// 자식 클래스
public class Rectangle extends Figure{
final double length;
final double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double area() {
return length * width;
}
}
상속으로 할 경우 다음이 해결되었다.
- 태그에 따른 불필요한 필드 → 상속으로 분리되어, 각 클래스는 필요한 것만 가짐.
- 공통 메서드의 Switch case → 상속으로 분리되어 분기문이 없어짐.
각 하위 클래스는 필요로 하는 필드만 가지고 있다. 따라서 모든 필드를 final로 처리하고, 클래스가 생성될 때 안전하게 초기화를 할 수도 있다. 또한 이전 Figure에 존재하던 쓸데없는 If / Switch 문이 없어지게 된다. 만약 If / Switch문이 많다면, 한번쯤 "내가 이 클래스에 너무 많은 책임을 넣은 것은 아닐까?"를 고민해봐야 한다.
정리
- 태그가 달린 클래스는 클래스 내부의 변수에 의해 클래스 전체의 동작이 매번 바뀌는 클래스를 의미한다.
- 태그가 달린 클래스는 단점이 너무 많기 때문에 클래스 계층 구조(상속)으로 바꾸면 해결할 수 있다.
- 만약 클래스 내부에 너무 많은 Switch, If 분기문이 있다면 태그 달린 클래스가 있는지 확인하고 구조를 변경한다.
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
Effective Java : 아이템24. 완벽공략 - 어댑터 패턴 (0) | 2023.04.15 |
---|---|
Effective Java : 아이템24. 멤버 클래스는 되도록 static으로 만들라. (0) | 2023.04.15 |
Effective Java : 아이템22. 인터페이스는 타입을 정의하는 용도로만 사용하라. (0) | 2023.04.12 |
Effective Java : 아이템21. 완벽공략 38. ConcurrentModificationException (0) | 2023.04.12 |
Effective Java : 아이템21. 인터페이스는 구현하는 쪽을 생각해 설계하라. (0) | 2023.04.12 |