Java ‐ 태그 달린 클래스보다는 클래스 계층구조를 활용하라[Effective Java Item 23] - dnwls16071/Backend_Summary GitHub Wiki

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

태그 달린 클래스란?

  • 태그 달린 클래스(tagged class)는 하나의 클래스 안에 여러 개의 객체 유형을 담고, 어떤 유형인지 구분하기 위해 '태그' 역할을 하는 특별한 필드를 두는 방식이다.
// Bad
public class Figure {
    enum Shape { RECTANGLE, CIRCLE }; // 태그 필드
    final Shape shape;

    // 사각형일 때 사용되는 필드
    double length;
    double width;

    // 원일 때 사용되는 필드
    double radius;

    // 원을 위한 생성자
    public Figure(double radius) {
        this.shape = Shape.CIRCLE;
        this.radius = radius;
    }

    // 사각형을 위한 생성자
    public Figure(double length, double width) {
        this.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);
        }
    }
}

태그 달린 클래스의 문제점은 여러가지가 있다.

  • 복잡한 코드: 여러 종류의 객체를 하나의 클래스에서 처리하므로 코드가 복잡하고 가독성이 떨어진다.
  • 유지보수 어려움: 새로운 유형을 추가할 때마다 클래스와 switch 문 같은 조건문을 모두 수정해야한다.
  • 메모리 낭비: 특정 유형에서만 사용되는 필드들이 다른 유형의 객체에도 모두 존재하므로 메모리가 낭비된다.
  • 안전성 문제: 프로그래머의 실수로 잘못된 태그 값을 사용하거나, 올바르지 않은 필드에 접근할 위험이 있다.

클래스 계층구조로 리팩토링

// Good
public abstract class Figure { // 계층구조의 루트가 될 추상 클래스를 먼저 정의한다.
    abstract double area();    // 태그 값에 따라 동작이 달라지는 메서드들을 추상 메서드로 선언한다.
}

class Circle extends Figure {  // 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문으로 인해 런타임 오류가 발생할 일도 없다.
  • 루트 클래스 코드를 건드리지 않고도 독립적으로 계층을 확장하고 함께 사용할 수 있게 된다.
  • 타입이 의미별로 따로 존재하니 변수 의미를 명시하거나 제한할 수 있고 또 특정 의미만 매개변수로 받을 수 있다.