Java

[Effective Java] 4-2. 클래스와 인터페이스: 상속보다는 컴포지션을 사용하라

noahkim_ 2024. 12. 27. 19:16

조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.


1. 상속 주의사항

재정의할 수 있는 메서드들이 내부적으로 어떻게 사용되는지 문서화하기

API 호출에 사용되는 경우
/**
 * Removes all elements from this collection.
 * 
 * Implementation Requirements:
 * This method internally calls `removeRange(0, size())`.
 * If `removeRange` is overridden in a subclass, the behavior of this method 
 * will change accordingly.
 */
public void clear() {
    removeRange(0, size());
}
  • Implementation Requirements 적어주기
    • 호출 순서
    • 호출 결과의 영향
  • 예상치 못한 동작 변경이 없도록 설계해야 함

 

생성자는 재정의 가능한 메서드를 호출해서는 안됨

public class Super {
    public Super() {
        overrideMe(); // 재정의된 메서드 호출
    }

    public void overrideMe() {
        System.out.println("super method");
    }
}

public class Sub extends Super {
    private String str; // 초기화되지 않은 상태

    public Sub() {
        str = "Sub String"; // 여기서 초기화됨
    }

    @Override
    public void overrideMe() {
        System.out.println(str); // str이 초기화되지 않았으므로 null 출력
    }

    public static void main(String[] args) {
        Sub sub = new Sub();
    }
}
  • 상위 클래스의 생성자 내부에서 호출한 메서드가 하위 클래스에서 재정의된 경우, 하위 클래스의 메서드가 호출됨
  • 하지만 이 시점에서 하위 클래스의 생성자가 아직 완료되지 않아 예기치 않은 동작이 발생할 수 있음

 

훅 메서드는 protected로 보호하기

protected void removeRange(int fromIndex, int toIndex) {
    ListIterator<E> it = listIterator(fromIndex);
    for (int i = 0, n = toIndex - fromIndex; i < n; i++) {
        it.next();
        it.remove();
    }
}
  • 상속받은 클래스가 원하는 동작을 추가하거나 변경할 수 있도록 공개된 메서드
  • 하위 클래스에서 재정의하여 중간에 동작을 삽입할 수 있는 적절한 훅 메서드를 제공해야 함
  • protected로 공개하여 하위 클래스가 재정의할 수 있도록 설계

 

2. 컴포지션 사용하기

상속은 캡슐화를 깨뜨림

  • 상속은 하위 클래스가 상위 클래스의 구현 세부 사항에 의존하게 만듬
  • 상위 클래스의 내부가 변경되면 하위 클래스의 동작에 이상이 생길 수 있음

 

컴포지션 사용

  • 기존 클래스를 확장하는 대신, 다른 클래스의 인스턴스를 private 필드로 포함하여 기능을 확장

 

Wrapper Class
Decorator Pattern