Published:
Updated:

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

상속과 합성

  • 상속과 합성은 객체지향 프로그래밍에서 가장 널리 사용되는 코드 재사용 기법
  • 상속이 부모 클래스로 자식 클래스를 연결하여 부모 클래스의 코드를 재사용하는 데 비해
    • 합성은 전체를 표현하는 객체가 부분을 표현하는 객체를 포함해서 부분 객체의 코드를 재사용함
  • 상속에서 부모 클래스와 자식 클래스 사이의 의존성은 컴파일타임에 해결되지만
    • 합성에서 두 객체 사이의 의존성은 런타임에 해결됨
  • 상속 관계는 is-a 관계라고 부르고
    • 합성 관계는 has-a 관계라고 부름
  • 상속과 합성은 코드 재사용이라는 동일한 목적을 가진다는 점을 제외하면
    • 구현 방법부터 변경을 다루는 방식에 이르기까지 모든 면에서 도드라진 차이를 보임

상속

  • 상속을 이용하면 자식 클래스의 정의에 부모 클래스의 이름을 덧붙이는 것만으로
    • 부모 클래스의 코드를 재사용할 수 있게 됨
  • 상속을 통해 자식 클래스는 부모 클래스의 정의 대부분을 물려받게 되며
    • 부모 클래스와 다른 부분만 추가하거나 재정의함으로써 기존 코드를 쉽게 확장할 수 있음
  • 그러나 상속을 제대로 활용하기 위해서는 부모 클래스의 내부 구현에 상세히 알아야 함
    • 즉, 자식 클래스와 부모 클래스 사이의 결합도가 높아질 수밖에 없음
  • 결과적으로 상속은 간단한 방법일이는 몰라도 우아한 방법이라고 할 수는 없음

합성

  • 합성은 구현에 의존하지 않는다는 점에서 상속과는 다름
    • 합성은 내부에 포함되는 객체의 구현이 아닌 public interface에 의존함
  • 즉, 합성을 이용하면 포함된 객체의 내부 구현이 변경되더라도 영향을 최소화 할 수 있기 때문에
    • 변경에 더 안정적인 코드를 얻을 수 있게 됨

상속과 합성의 차이점

  • 상속 관계는 클래스 사이의 정적인 관계인 데 비해
    • 합성 관계는 객체 사이의 동적인 관계
      • 이 차이점이 중요한 이유는
        • 코드 작성 시점에 결정한 상속 관계는 변경이 불가능하지만
          • 합성 관계는 실행 시점에 동적으로 변결할 수 있기 때문에
      • 따라서 상속 대신 합성을 사용하면 변경하기 쉽고 유연한 설계를 얻을 수 있음

[코드 재사용을 위해서는] 객체 합성이 클래스 상속보다 더 좋은 방법이다.

  • 상속과 합성은 재사용의 대상이 다름
    • 상속은 부모 클래스 안에 구현된 코드 자체를 재사용하지만
      • 합성은 포함되는 객체의 public interface를 재사용함
    • 즉, 상속 대신 합성을 사용하면 구현에 대한 의존성을
      • 인터페이스에 대한 의존성으로 변경할 수 있음
    • 다시 말하면 클래스 사이의 높은 결합도를 객체 사이의 낮은 결합도로 대체 가능

스크린샷 2023-03-23 오전 12 12 23

상속을 합성으로 변경하기

스크린샷 2023-03-23 오전 12 13 56

  • 합성을 사용하면 위의 상속이 초래하는 세 가지 문제점을 해결할 수 있음
  • 상속을 합성으로 바꾸는 방법을 매우 간단한데
    • 자식 클래스에 선언된 상속 관계를 제거하고 부모 클래스의 인스턴스를 자식 클래스의 인스턴스 변수로 선언하면 됨

불필요한 인터페이스 상속 문제

  • Hashtable 클래스와 Properties 클래스 사이의 상속 관계를 합성 관계를 바꿀 수 있음
    • Properties 클래스에서 상속 관계를 제거하고 HashtableProperties의 인스턴스 변수로 포함시키면 됨
  • 내부 구현에 밀접하게 결합되는 상속과 달리
    • 합성으로 변경한 PropertiesHashtable의 내부 구현에 관해 알지 못함

메서드 오버라이딩의 오작용 문제

  • InstrumentedHashSet도 같은 방법을 사용해서 합성 관계로 변경할 수 있음
    • HashSet 인스턴스를 내부에 포함한 후 HashSetpublic interface에서 제공하는 오퍼레이션 이용
  • InstrumentedHashSet의 코드
    • Set의 오퍼레이션을 오버라이딩한 인스턴스 메서드에서 내부의 HashSet 인스턴스에게 동일한 메서드 호출 전달
    • 이를 포워딩(forwarding) 이라 부름
    • 동일한 메서드를 호출하기 위해 추가된 메서드를 포워딩 메서드(forwarding method) 라고 부름

부모 클래스와 자식 클래스의 동시 수정 문제

  • Playlist의 경우에는 합성으로 변경하더라도
    • 가수별 노래 목록을 유지하기 위해 PlaylistPersonalPlaylist를 함께 수정해야 하는 문제가 해결되지 않음
  • 그렇다 하더라도 여전히 상속보다는 합성을 사용하는 게 좋음
    • 향후에 Playlist의 내부 구현을 변경하더라도 파급 효과를 최대한 PersonalPlaylist 내부로 캡슐화할 수 있기 때문
  • 대부분의 경우, 구현에 대한 결합보다는 인터페이스에 대한 결합이 더 좋음

몽키 패치
몽키 패치(Monkey Patch)란 현재 실행 중인 환경에만 영향을 미치도록 지역적으로 코드를 수정하거나 확장하는 것을 가리킨다. 여러분에게 Playlist의 코드를 수정할 권한이 없거나 소스코드가 존재하지 않는다고 하더라도 몽키 패치가 지원되는 환경이라면 Playlist에 직접 remove 메서드를 추가하는 것이 가능하다. 자바는 언어 차원에서 몽키 패치를 지원하지 않기 때문에 바이트코드를 직접 변환하거나 AOP(Aspect-Oriented Programming)를 이용해 몽키 패치를 구현하고 있다.

상속으로 인한 조합의 폭발적인 증가

  • 상속으로 인해 결합도가 높아지면 코드를 수정하는 데 필요한 작업의 양이 과도하게 늘어나는 경향이 있음
    • 가장 일반적인 상황은 작은 기능들을 조합해서 더 큰 기능을 수행하는 객체를 만들어야 하는 경우
    • 일반적으로 다음과 같은 두 가지 문제점이 발생
      • 하나의 기능을 추가하거나 수정하기 위해 불필요하게 많은 수의 클래스를 추가하거나 수정해야 함
      • 단일 상속만 지원하는 언어에서는 상속으로 인해 오히려 중복 코드의 양이 늘어날 수 있음
  • 합성을 사용하면 상속으로 인해 발생하는 클래스의 증가와 중복 코드 문제를 간단하게 해결할 수 있음

추상 메서드와 훅 메서드

  • 부모 클래스에 추상 메서드를 추가하면 모든 자식 클래스들이 추상 메서드를 오버라이딩 해야 하는 문제 발생
    • 자식 클래스의 수가 적다면 큰 문제는 아니겠지만, 많을 경우에는 꽤나 번거로운 일이 됨

스크린샷 2023-03-23 오전 11 23 19

상속의 남용

  • 상속의 남용으로 하나의 기능을 추가하기 위해 필요 이상으로 많은 수의 클래스를 추가해야 하는 경우
    • 클래스 폭발(class explosion) 문제
      • 자식 클래스가 부모 클래스의 구현에 강하게 결합되도록 강요하는 상속의 근본적인 한계 때문에 발생하는 문제
      • 컴파일 타임에 결정된 자식 클래스와 부모 클래스 사이의 관계를 변경될 수 없으므로
        • 자식 클래스와 부모 클래스의 다양한 조합이 필요한 상황에서 유일한 해결 방법은 조합의 수만큼 새로운 클래스를 추가하는 방법뿐
      • 클래스 폭발 문제는 새로운 기능을 추가할 때뿐만 아니라 기능을 수정할 때도 문제가 됨
        • 이 문제를 해결할 수 있는 최선의 방법을 상속을 포기하는 것
    • 조합의 폭발(combinational explosion) 문제

합성 관계로 변경하기

  • 상속 관계는 컴파일 타임에 결정되고 고정되기 때문에 코드를 실행하는 도중에는 변경할 수 없음
    • 즉, 여러 기능을 조합해야 하는 설계에 상속을 이용하면 모든 조합 가능한 경우별로 클래스를 추가해야 함
      • 이것이 클래스 폭발 문제임
  • 합성은 컴파일 타임 관계를 런타임 관계로 변경함으로써 이 문제를 해결
    • 합성을 사용하면 구현이 아닌, public interface에 대해서만 의존할 수 있으므로 런타임에 객체의 관계 변경 가능
  • 컴파일 타임 의존성과 런타임 의존성의 거리가 멀수록 설계가 유연해짐
    • 상속을 사용하는 것은 컴파일 타임의 의존성과 런타임의 의존성을 동일하게 만들겠다고 선언하는 것
      • 즉, 상속을 사용하면 부모 클래스와 자식 클래스 사이의 관계가 정적으로 고정되기 때문에
        • 실행 시점에 동적으로 관계를 변경할 수 있는 방법이 없음
  • 상속과 달리 합성 관계는 런타임에 동적으로 변경할 수 있음
    • 합성을 사용하면 컴파일 타임 의존성과 런타임 의존성을 다르게 만들 수 있음
    • 클래스 폭발 문제를 해결하기 위해 합성을 사용하는 이유는
      • 런타임에 객체 사이의 의존성을 자유롭게 변경할 수 있기 때문
  • 합성을 사용하면 구현 시점에 정책들의 관계를 고정시킬 필요가 없으며
    • 실행 시점에 정책들의 관계를 유연하게 변경할 수 있게 됨
  • 상속이 조합의 결과를 개별 클래스 안으로 밀어 넣는 방법이라면
    • 합성은 조합을 구성하는 요소들을 개별 클래스로 구현한 후
      • 실행 시점에 인스턴스를 조립하는 방법을 사용하는 것
    • 컴파일 의존성에 속박되지 않고 다양한 방식의 런타임 의존성을 구성할 수 있다는 것이
      • 합성이 제공하는 가장 커다란 장점
  • 컴파일 타임 의존성과 런타임 의존성의 거리가 멀면 멀수록 설계의 복잡도는 상승하므로 코드를 이해하기는 어려워짐
    • 하지만 설계는 변경과 유지보수를 위해 존재함
    • 대부분의 경우에는 단순한 설계가 정답이지만 변경의 복잡성으로 인해 유연성이 더 중요함

객체 합성이 클래스 상속보다 더 좋은 방법이다

  • 객체지향에 코드를 재사용하기 위해 가장 널리 사용되는 방법은 상속
    • 하지만 상속은 코드 재사용을 위한 우아한 해결책은 아님
    • 상속은 부모 클래스의 세부적인 구현에 자식 클래스를 강하게 결합시키기 때문에 코드의 진화를 방해함
  • 코드를 재사용하면서도 건전한 결합도를 유지할 수 있는 더 좋은 방법은 합성을 이용하는 것
    • 상속이 구현을 재사용하는 데 비해 합성은 객체의 인터페이스를 사용

믹스인

  • 믹스인이라는 이름으로 널리 알려져 있는 이 기법은
    • 상속과 합성의 특성을 모두 보유하고 있는 독특한 코드 재사용 방법
  • 믹스인을 이해하고 나면 상속과 합성의 장단점에 관해 좀 더 깊이 있게 이해할 수 있음
  • 상속을 사용하면 다른 클래스를 간편하게 재사용하고 점진적으로 확장할 수 있지만
    • 부모 클래스와 자식 클래스가 강하게 결합되기 때문에 수정과 확정에 취약한 설계를 낳게 됨
  • 우리가 원하는 것은 코드를 재사용하면서도 납득할 만한 결합도를 유지하는 것
  • 합성이 상속과 같은 문제점을 초래하지 않는 이유는
    • 클래스의 구체적인 구현이 아니라 객체의 추상적인 인터페이스에 의존하기 때문
  • 상속과 클래스를 기반으로 하는 재사용 방법을 사용하면
    • 클래스의 확장과 수정을 일관성 있게 표현할 수 있는 추상화의 부족으로 인해 변경하기 어려운 코드를 얻게 됨
    • 즉, 구체적인 코드를 재사용하면서도 낮은 결합도를 유지할 수 있는 유일한 방법은 재사용에 적합한 추상화를 도입하는 것
  • 믹스인(mixin) 은 객체를 생성할 때 코드 일부를 클래스 안에 섞어 넣어 재사용하는 기법을 가리키는 용어
    • 합성이 실행 시점에 객체를 조합하는 재사용 방법이라면
      • 믹스인은 컴파일 시점에 필요한 코드 조각을 조합하는 재사용 방법
  • 상속이 클래스와 클래스 사이의 관계를 고정시키는 데 비해
    • 믹스인은 유연하게 관계를 재구성할 수 있음
    • 믹스인은 코드 재사용에 특화된 방법이면서도 상속과 같은 결합도 문제를 초래하지 않음
    • 믹스인은 합성처럼 유연하면서도 상속처럼 쉽게 코드를 재사용할 수 있는 방법
  • 그 방법이 무엇이건 코드를 다른 코드 안에 유연하게 섞어 넣을 수 있다면 믹스인이라고 부를 수 있음

쌓을 수 있는 변경

  • 전통적으로 믹스인은 특정한 클래스의 메서드를 재사용하고 기능을 확장하기 위해 사용돼 옴
  • 믹스인은 상속 계층 안에서 확장한 클래스보다 더 하위에 위치하게 됨
    • 즉, 믹스인은 대상 클래스의 자식 클래스처럼 사용될 용도로 만들어지는 것
    • 따라서 믹스인을 추상 서브클래스(abstract subclass) 라고 부르기도 함

스크린샷 2023-03-23 오전 11 44 25

  • 믹스인을 사용하면 특정한 클래스에 대한 변경 또는 확장을 독립적으로 구현한 후 필요한 시점에 차례대로 추가할 수 있음
    • 믹스인의 이러한 특징을 쌓을 수 있는 변경(stackable modification) 이라고 부를 수 있음

스크린샷 2023-03-23 오전 11 45 23

Tags:

Categories:

Published:
Updated:

Leave a comment