Published:
Updated:

조영호 저자님의 “오브젝트”를 기반으로 쓴 글입니다.

상속과 합성

  • 객체지향 프로그래밍의 장점 중 하나는 코드를 재사용하기가 용이하다는 것
  • 객체지향에서는 코드를 재사용하기 위해 “새로운” 코드를 추가
  • 객체지향에서의 코드는 일반적으로 클래스 안에 작성됨
  • 객체지향에서 클래스를 재사용하는 전통적인 방법은 새로운 클래스를 추가하는 것
    • 이 대표적인 기법은 “상속”
      • 재사용 관점에서의 상속: 클래스 안에 정된 인스턴스 변수와 메서드를 자동으로 새로운 클래스에 추가하는 구현 기법
  • 객체지향에서 상속 외에도 코드를 효과적으로 재사용할 수 있는 방법이 한 가지 더 있음
    • 새로운 클래스의 인스턴스 안에 기존 클래스의 인스턴스를 포함시키는 방법으로 “합성”이라 부름

DRY 원칙

“일단 새로운 코드를 추가하고 나면 언젠가는 변경될 것”

  • 중복 코드는 “변경”을 방해
    • 위와 같은 이유가 중복 코드를 제거해야 하는 가장 큰 부분을 차지함
  • 중복 코드를 갖고 있다면, 코드를 수정하는 데 필요한 노력이 몇 배는 증가함
    • 그 모든 코드를 찾아내기도 어렵고, 찾아낸다 한들 일관되게 수정까지 해야 함
  • 중복 여부를 판단하는 기준 -> “변경”
    • 요구사항이 변경되었을 때 두 코드를 함께 수정해야 한다면 이 코드는 중복임
  • 위와 같은 중복 코드를 지양하기 위해 프로그래머들은 “DRY 원칙”을 따라야 함
    • Once and Only Once 원칙[Beck96]
    • Single-Point Control 원칙[Glass06b]

DRY 원칙 (Don’t Repeat Yourself)
모든 지식은 시스템 내에서 단일하고, 애매하지 않고, 정말로 믿을 만한 표형 양식을 가져야 한다.

상속을 이용해서 중복 코드 제거

  • 상속의 기본 아이디어
    • 이미 존재하는 클래스와 유사한 클래스가 필요하다면 코드를 복사하지 않고, 상속을 이용해 코드를 재사용
  • 하지만 상속을 염두에 두고 설계되지 않은 클래스를 상속을 이용해 재사용하는 것은 쉽지 않음
  • 상속은 결합도를 높이고, 상속이 초래하는 부모 클래스와 자식 클래스 사이의 강한 결합이 코드를 수정하기 어렵게 만듦

상속을 위한 경고 1
자식 클래스의 메서드 안에서 super 참조를 이용해 부모 클래스의 메서드를 직접 호출할 경우 두 클래스는 강하게 결합되므로 super 호출을 제거할 수 있는 방법을 찾아 결합도를 제거해라.

  • 상속을 사용하면 적은 노력으로도 새로운 기능을 쉽고 빠르게 추가할 수 있음
    • 하지만 그로 인해 커다란 대가를 치러야 할 수 있음
  • 이처럼 상속 관계로 연결된 자식 클래스가 부모 클래스의 변경에 취약해지는 현상을 가리켜 “취약한 기반 클래스 문제”라고 부름

취약한 기반 클래스 문제

  • 상속은 자식 클래스와 부모 클래스의 결합도를 높임
    • 이 강한 결합도로 인해 자식 클래스는 부모 클래스의 불필요한 세부사항에 엮이게 됨
    • 부모 클래스의 작은 변경에도 자식 클래스는 컴파일 오류와 실행 에러라는 고통에 시달릴 수 있음
  • 이처럼 부모의 클래스의 변경에 의해 자식 클래스가 영향을 받는 현상을 바로
    • 취약한 기반 클래스 문제(Fragile Base Class Problem, Brittle Base Class Problem)라고 부름
    • 이 문제는 상속을 사용한다면 피할 수 없는 객체지향 프로그래밍의 근본적인 취약성임

스크린샷 2023-03-22 오후 5 32 18

  • 취약한 기반 클래스 문제는 캡슐화를 약화시키고 결합도를 높임
    • 상속은 자식 클래스가 부모 클래스의 구현 세부사항에 의존하도록 만들기 때문에 캡슐화를 약화시킴.
    • 이것이 상속이 위험한 이유인 동시에 우리가 상속을 피해야 하는 첫 번째 이유
  • 객체를 사용하는 이유는 구현과 관련된 세부사항을 public interface 뒤로 캡슐화할 수 있기 때문
    • 캡슐화는 변경에 의한 파급 효과를 제어할 수 있기 때문에 가치가 있음
    • 객체는 변경될지도 모르는 불안정한 요소를 캡슐화함으로써 파급효과를 걱정하지 않고도 자유롭게 내부 변경 가능
  • 상속을 사용하면 부모 클래스의 public interface가 아닌 구현을 변경하더라도 자식 클래스가 영향을 받기 쉬워짐
    • 상속 계층의 상위에 위치한 클래스에 가해지는 작은 변경만으로도 상속 계층에 속한 자손들이 급격하게 요동칠 수 있음
  • 객체지향의 기반 -> 캡슐화를 통한 변경의 통제
    • 상속은 코드의 재사용을 위해 캡슐화의 장점을 희석시키고 구현에 대한 결합도를 높임으로써 객체지향이 가진 강력함을 반감시킴

상속이 가지는 문제점

하나씩 타이틀을 빼서 다 설명하려 했는데 너무 길어져 간단하게만

  • 불필요한 인터페이스 상속 문제
    • 자바 초기 버전에서 상속을 대표적인 사례
      • Properties, Stack
    • StackVector의 자식 클래스로 구현함
      • StackVector를 상속받기 때문에 Stackpublic interfaceVectorpublic interface가 합쳐짐
    • 아 이래서 Vector를 쓰지 말라고 하는 거구나

상속을 위한 경고 2
상속 받은 부모 클래스의 메서드가 자식 클래스의 내부 구조에 대한 규칙을 깨뜨릴 수 있다.


  • 메서드 오버라이딩의 오작용 문제
    • HashSet의 구현에 강하게 결합된 InstrumentedHashSet 클래스
      • HashSet의 내부에 저장된 요소의 수를 셀 수 있는 기능을 추가한 클래스로서 HashSet의 자식 클래스로 구현됨
      • 미래에 발생할지 모르는 위험을 방지하기 위해 코드를 중복시킴
        • 오버라이딩된 allAll 메서드의 구현이 HashSet의 것과 동일
  • 클래스가 상속되기를 원한다면 상속을 위해 클래스를 설계하고 문서화해야 하며
    • 그렇지 않은 경우에는 상속을 금지시켜야 함

스크린샷 2023-03-22 오후 9 33 31

상속을 위한 경고 3
자식 클래스가 부모 클래스의 메서드를 오버라이딩할 경우 부모 클래스가 자신의 메서드를 사용하는 방법에 자식 클래스가 결합될 수 있다.


  • 부모 클래스와 자식 클래스의 동시 수정 문제
    • 자식 클래스가 부모 클래스의 메서드를 오버라이딩하거나 불필요한 인터페이스를 상속받지 않았음에도
      • 부모 클래스를 수정할 때 자식 클래스를 함께 수정해야 함
    • 상속을 사용하면 자식 클래스가 부모 클래스의 구현에 강하게 결합되기 때문에 이 문제를 피하기는 어려움
    • 결합도란, 다른 대상에 대해 알고 있는 지식의 양
      • 상속은 기본적으로 부모 클래스의 구현을 재사용한다는 기본 전제를 따르기 때문에
        • 자식 클래스가 부모 클래스의 내부에 대해 알도록 강요함
      • 코드 재사용을 위한 상속은 부모 클래스와 자식 클래스를 강하게 결합시키기 때문에
        • 함께 수정해야 하는 상황 역시 빈번하게 발생할 수밖에 없음

스크린샷 2023-03-22 오후 9 38 33

상속을 위한 경고 4
클래스를 상속하면 결합도로 인해 자식 클래스와 부모 클래스의 구현을 영원히 변경하지 않거나,
자식 클래스와 부모 클래스를 동시에 변경하거나 둘 중 하나를 선택할 수밖에 없다.

추상화에 의존하자

  • 서로 강하게 결합돼 있을 경우, 이 문제를 해결하는 가장 일반적인 방법은
    • 자식 클래스가 부모 클래스의 구현이 아닌 추상화에 의존하도록 만드는 것
    • 정확히 말하면 부모 클래스와 자식 클래스 모두 추상화로 의존하도록 수정해야 함
  • 코드 중복을 제거하기 위해 상속을 도입할 때 따르는 두 가지 원칙
    • 두 메서드가 유사하게 보인다면 차이점을 메서드로 추출하라.
      • 메서드 추출을 통해 두 메서드를 동일한 형태로 보이도록 만들 수 있다[Feathers04].
    • 부모 클래스의 코드를 하위로 내리지 말고 자식 클래스의 코드를 상위로 올려라.
      • 부모 클래스의 구체적인 메서드를 자식 클래스로 내리는 것보다
        • 자식 클래스의 추상적인 메서드를 부모 클래스로 올리는 것이
          • 재사용성과 응집도 측면에서 더 뛰어난 결과를 얻을 수 있다[Metz12].

차이를 메서드로 추출하라

  • 가장 먼저 할 일은 중복 코드 안에서 차이점을 별도의 메서드로 추출하는 것
  • 그러면 두 클래스의 메서드는 완전히 동일해지고, 추출한 메서드 안에 서로 다른 부분을 격리시켜놓을 수 있는데
    • 이 같은 코드를 부모 클래스로 올리는 일만 남게 됨

중복 코드를 부모 클래스로 올려라

  • 자식 클래스들 사이의 공통점을 부모 클래스로 옮긺으로써 상속 계층을 구성할 수 있음
  • 이제 이 설계는 추상화에 의존하게 됨

스크린샷 2023-03-22 오후 11 47 47

차이에 의한 프로그래밍

  • 상속을 사용하면 이미 존재하는 클래스의 코드를 기반으로 다른 부분을 구현함으로써 새로운 기능을 쉽고 빠르게 추가할 수 있음
  • 상속이 강력한 이유는 익숙한 개념을 이용해서 새로운 개념을 쉽고 빠르게 추가할 수 있기 때문
  • 이처럼 기존 코드와 다른 부분만을 추가함으로써 애플리케이션의 기능을 확장하는 방법을
    • 차이에 의한 프로그래밍(programming by difference)이라고 부름
    • 상속을 이용하면 이미 존재하는 클래스의 코드를 쉽게 재사용할 수 있기 때문에 애플리케이션의 점진적인 정의가 가능해짐
  • 차이에 의한 프로그래밍의 목표는 중복 코드를 제거하고 코드를 재사용하는 것
  • 중복 코드 제거코드의 재사용은 서로 다른 단어
    • 중복을 제거하기 위해서는 중복 코드를 제거해서 하나의 모듈로 모아야 함
    • 중복 코드를 제거하기 위해 최대한 코드를 재사용해야 함
      • 코드를 재사용하면 코드의 품질을 유지하면서도 코드를 작성하는 노력과 테스트를 줄일 수 있음
  • 객체지향 세계에서 중복 코드를 제거하고 코드를 재사용할 수 있는 가장 유명한 방법은 상속
    • 여러 클래스에 공통적으로 포함돼 있는 중복 코드를 하나의 클래스로 모으고
    • 원래 클래스에서 중복 코드를 제거한 후 중복 코드가 옮겨진 클래스를 상속 관계로 연결
    • 코드를 컴파일하면 무대 뒤에서 마법이 일어나 상속 관계로 연결된 코드들이 하나로 합쳐짐
    • 따라서 상속을 사용하면 여러 클래스 사이에서 재사용 가능한 코드를 하나의 클래스 안으로 모을 수 있음
  • 하지만! 상속의 오용과 남용은 애플리케이션을 이해하고 확장하기 어렵게 만듦
    • 정말로 필요한 경우에만 상속을 사용하자
  • 객체지향에 능숙한 개발자들은 상속의 단점을 피하면서도 코드를 재사용할 수 있는 더 좋은 방법을 알고 있음
    • 그것은 바로 합성

Tags:

Categories:

Published:
Updated:

Leave a comment