[아이템 23] 태그 달린 클래스보다는 클래스 계층구조를 활용하라

Jimin Lim·2022년 5월 31일
0

Effective Java

목록 보기
23/38
post-thumbnail

아이템 23

태그 달린 클래스보다는 클래스 계층 구조를 활용하라

태그 달린 클래스

현재 표현하는 의미를 태그 값으로 알려주는 클래스를 본 적이 있을 것이다. 예를 들어, 모양을 태그로 나타내는 클래스가 있다.

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);
        }
    }
}
  • enum을 이용해 현재 태그를 표현하고 있다.
  • switch로 영역을 계산하고 있다.

태그 달린 클래스의 문제점

이 코드는 여러 단점이 존재한다.

  1. 여러 구현이 한 클래스에 혼합되어 있어 가독성이 나쁘다.
  2. 사용하지 않는 부분도 함게 하니 메모리도 많이 사용한다.
    • final로 선언하려면 해당 의미에 쓰이지 않는 필드들도 생성자에서 초기화해야 한다.
  3. 생성자가 태그 필드를 설정하고 해당 의미에 쓰이는 데이터 필드들을 초기화하는데 컴파일러가 도와줄 수 있는 것은 없다.
  4. 다른 의미를 추가하려면 코드도 수정해야 한다.
    • 의미에 따른 switch문을 찾아 새 의미를 처리하는 코드를 추가해야 한다.
  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;

    Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    double area() {
        return length * width;
    }
}

계층 구조로 나타내면 태그 달린 클래스의 문제점을 해결할 수 있다.

  1. 루트 클래스를 상속받아 한 기능을 한 클래스가 하도록 하였다.
  2. 쓸데없는 코드도 모두 사라졌다.
    • 독립된 클래스에 담아 관련 없던 데이터 필드를 모두 제거했다.
    • 또한 살아남은 필드들은 모두 final이다.
  3. 각 클래스의 생성자는 모든 필드를 남김없이 초기화하고 추상 메서드를 모두 구현했는지 컴파일러가 확인해준다.
  4. case 문 때문에 런타임 오류가 발생할 일이 없다.
    • 루트 클래스를 건드리지 않고 독립적으로 계층 구조를 확장시킬 수 있다.
  5. 인스턴스로 타입을 판단 가능하다.

만약 정사각형을 추가하고자 한다면 아래와 같이 간단하게 반영할 수 있다.

class Square extends Rectangle {
    Square(double side) {
        super(side, side);
    }
}

정리

태그 달린 클래스를 써야하는 상황은 거의 없다. 새로운 클래스를 작성하는 데 태그 필드가 등장한다면 태그를 없애고 계층 구조를 대체하는 방법을 생각해보자.

profile
💻 ☕️ 🏝 🍑 🍹 🏊‍♀️

0개의 댓글