item18. 상속보다는 컴포지션을 사용하라

Jay·2023년 10월 13일
0

<이펙티브 자바>의 item18을 읽고 요약 및 보충한 글입니다.

상속을 지양해야되는 이유

상위 클래스의 릴리즈로 하위 클래스가 영향을 받을 수 있다.
1. 상위 클래스에서 구현한 신규 메서드가 하위클래스의 로직을 방해하는 경우

    public class CustomList {

      private List elementData = new ArrayList();

      public boolean add(String s) {
          elementData.add(s);
          return true;
      }

      @Override
      public String toString() {
          return "CustomList{" +
                  "elementData=" + elementData +
                  '}';
           }
    }
  
    public class CustomCollection extends CustomList {

      @Override
      public boolean add(String s) {
          if (isValidElement(s)) {
              return super.add(s);
          } else {
              throw new IllegalArgumentException("Invalid element");
          }
      }

      private boolean isValidElement(String s) {
          // 보안 조건 체크하는 로직
          if (s.equals("Invalid element")) {
              return false;
          }
          return true;
      }
    }

CustomList(상위 클래스), CustomCollection(하위 클래스)를 생성했다.
CustomCollection에서는 add 메서드를 통해 배열에 문자열을 넣을 수 있는데, Invalid element 문자열은 필터링 하고 있다.

@Test
    void testBeforeRelease() {
        CustomCollection customCollection = new CustomCollection();
        customCollection.add("ValidElement");  // 특정 조건을 만족하는 원소 추가
        customCollection.add("AnotherValidElement");  // 특정 조건을 만족하는 원소 추가
        assertThatThrownBy(() -> customCollection.add("Invalid element"))  // 조건 불만족하는 원소 추가
                .isInstanceOf(IllegalArgumentException.class);
    }

이때 상위클래스에서 새로운 릴리즈 버전에 배열에 문자열을 추가하는 새로운 메서드를 구현했다.

public class CustomList {
  public boolean addOne(String s) {
          elementData.add(s);
          return true;
      }
}

이번 릴리즈를 통해 CustomCollection에서도 addOne 메서드를 통해 배열에 문자열을 추가하는 기능을 갖게 되었다. 기존에 CustomCollection 클래스는 Invalid element문자열은 필터링 하도록 구현이 되어 있었기에, 클라이언트에서 필터링을 무시할 수 있는 꼼수가 생긴것이다.

@Test
    void testAfterRelease() {
        CustomCollection customCollection = new CustomCollection();
        customCollection.add("ValidElement");  // 특정 조건을 만족하는 원소 추가
        customCollection.add("AnotherValidElement");  // 특정 조건을 만족하는 원소 추가
        customCollection.addOne("Invalid element");  // 조건 불만족하는 원소 추가 가능

        Assertions.assertThat(customCollection.toString()).contains("Invalid element");
    }

앞으로 상위 클래스에서는 배열에 문자열을 추가할 수 있는 다양한 메서드를 구현할지도 모르기 때문에, 하위 클래스에서는 계속 상위 클래스의 릴리즈를 주시해야 하고, 하위 클래스의 보안 조건(:Invalid elemnt는 배열에 추가될 수 없음)을 공고히 하기 위해 계속 오버라이딩을 해야한다. 즉, 상위 클래스의 릴리즈로 인해 하위 클래스의 기능이 오염되어 하위클래스의 캡슐화가 깨질 수 있다.

  1. 상위 클래스에서 새로운 메서드를 구현했는데 이미 같은 이름, 파라미터, 반환값을 가진 메서드가 하위 클래스에 구현이 되어있을 때.

    • 드문 경우지만 충분히 가능성이 있다. 하위 클래스에서 구현한 메서드가 상위 클래스와 같은 메서드 명, 파라미터, 반환값을 가지고 있어 하위클래스에서 해당 메서드를 오버라이드한 꼴이 된다. 하지만 하위 클래스에 존재하는 메서드가 먼저 구현되었으므로 하위 클래스의 메서드가 상위 클래스의 메서드를 확장해서 구현한 것은 아니다. 즉, 리스코프 치환 원칙을 준수하지 못할 가능성이 크다.

    • 같은 이름, 파라미터를 가진 메서드(반환값은 다른)를 상위클래스에서 구현했다면 컴파일 오류가 발생한다. 오버라이딩의 원칙을 어겼기 때문이다.

profile
You're not a computer, you're a tiny stone in a beautiful mosaic

0개의 댓글