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

yeom yaloo·2023년 11월 29일
0

Effective Java

목록 보기
11/20
post-thumbnail

4장 클래스와 인터페이스

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

  • 태그 달린 클래스가 무엇인지 알아보고 이에 대한 단점에 대해서 알아보자.
  • 이런 무수한 단점을 날려줄 클래스 계층 구조를 활용해서 태그 달린 클래스의 단점을 모두 상쇄시켜 사용해보자.

[핵심 정리]

  • 태그 달린 클래스를 사용할거라면 클래스 계층 구조를 활용해서 이를 대체하자 이는 완벽하게 태그 달린 클래스의 단점을 상쇄시키기 충분하다.
  • 태그 필드가 등장한다면 태그를 없애고 계층구조로 대체하자. 기존 코드가 태그 필드를 사용하고 있다면 이를 리팩토링하는 방안을 고려하자.

[태그 달린 클래스?]

1. 태그 달린 클래스란?

public class Figure {
    enum Shape {RECTANGLE, CIRCLE}

    //태그 필드 - 현재 모양을 나타낸다.
    private Shape shape;

    // 다음 필드들은 모양이 사각형일 때만 사용.
    private double length;
    private double width;

    // 다음 필드들은 모양이 원일 때만 싸용.
    private double radius;

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

    //사각형용 생성자
    public Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    private double area() {
        switch (shape) {
            case RECTANGLE:
                return length + width;
            case CIRCLE:
                return Math.PI * (radius * radius);
            default:
                throw new AssertionError(shape);
        }
    }
}
  • 두 가지 이상의 의미를 표현할 수 있다.

2. 태그 달린 클래스의 단점

2-1. 쓸데 없는 코드가 많다.

  • 열거 타입 선언, 태그 필드, switch 문 등 쓸데 없는 코드가 많다.

2-2. 여러 구현의 혼합으로 가독성이 나쁘다.

  • 여러 구현이 가득해서 이러한 구현들 때문에 코드의 가독성을 해친다.

2-3. 메모리 사용이 많다.

  • 다른 의미를 위한 코드들이 득실대서 메모리 사용도 많다.

2-4. 컴파일시 문제를 찾기 어렵다

  • 데이터 필드를 초기화하는 데 컴파일 시 해당 클래스에 문제가 있어도 문제를 찾을 수 없어 런타임시에나 문제가 발견되는 경우가 많다.

2-5. 새로 코드를 추가하기 어렵다.

  • 새로운 의미 추가를 위해서 코드를 추가하게 되면 컴파일시에 문제가 잘 드러나지 않고 런타임시에나 문제가 드러난다.

2-6. 인스턴스의 타입만으로 현재 나타내는 의미를 알 수 없다.

[태그 달린 클래스를 대신해서 클래스 계층구조를 이용한 방식을 활용하자]

1. 태그 달린 클래스를 클래스 계층구조를 이용해서 같은 의미로 사용할 수 있는 방법

1-1. root를 추상 클래스로 하나 정의한다.

1-2. 태그 값에 따라서 달라지는 메서드들을 루트 클래스의 추상 메서드로 선언한다.

  • 위의 태그 달린 클래스의 area()가 해당 작업에 해당한다.
    • 태그값 shape에 따라서 달라지는 메서드

1-3. 태그 값에 따라서 달라지지 않고 일정하게 동작하는 메서드의 경우엔 루트 클래스(추상 클래스)에 일반 메서들 추가한다.

  • 일정하게 동작하는 메서드뿐만 아니라 공통으로 사용하는 데이터 필드들도 전부 루트 클래스로 올린다.
  • 위의 태그 달린 클래스 안에는 일정하게 동작하는 메서드도, 공통 사용 필드도 없다.
  • 클래스 계층구조를 이용해서 이를 사용한다면 area()만 추상 클래스 안에 추상 메서드로 넣어두면 된다.

1-4. 루트 클래스를 확장한 구현체를(=구체 클래스) 하나씩 정의해라

  • 각 하위 클래스에는 각자 의미에 해당하는 데이터 필드들을 넣는다.
  • 예를 들면 원에는 반지름, 사각형에는 길이와 너비 등을 넣어준다.

2. 계층구조를 이용한 방식 코드


// root를 추상 클래스로 만든다
abstract class Figure {

  // 태그 값에 따라서 동작이 달라지는 area() 메서드의 경우엔 추상 메서드로 선언해주자
  abstract dobule 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. 쓸데 없는 코드삭제

  • 사용하지도 않는 필드는 root 클래스 내에 존재하지 않고 필요에 따라서 구현체에 이를 넣어주고 있다.
  • 또한 생성자 역시 굳이 태그에 따라서 달아줄 필요가 없다.
  • 필요 없는 shap 정의를 위한 enum 이 사라졌다
  • 이는 Figure circle = new Circle(); 이런 식의 다형성을 이용한 방식을 쓰면 간단해진다.

2. 추상 메서드 구현을 컴파일러가 해준다.

  • 기존 컴파일러는 아무것도 하지 않아서 런타임시 오류가 있음을 발견할 수 있었는데 계층구조를 이용해서 해당 작업을 진행하니 컴파일 시 해당 문제가 있는지 확인할 수 있게 됐다.

3. 유연한 설계가 가능한 계층구조를 활용한 방안

  • 기존의 방식은 어딘가 단점이 많은 부분이 있다면 지금 클래스의 계층구조를 활용해서 이를 대체한 방식은 유연하게 사용이 가능해졌다.
profile
즐겁고 괴로운 개발😎

0개의 댓글