TL;DR

  • 2장에서는 책임을 중심으로 설계된 객체지향 코드의 대력적인 모양을 살펴봅니다.
  • 객체지향 프로그래밍에서 사용되는 다양한 요소와 개념을 이해하게 됩니다.

01. 영화 예매 시스템

요구사항 살펴보기

02 객체지향 프로그래밍을 향해

협력, 객체, 클래스

  • 객체지향은 객체를 지향하는 것입니다. C++, 자바, 루비, C#과 같이 클래스 기반의 객체지향 언어에 익숙한 사람이라면 가장 먼저 어떤 Class(클래스)가 필요한지 고민할 것입니다. 대부분의 사람들은 클래스를 결정한 후에 클래스에 어떤 속성과 메서드가 필요한지 고민합니다. 하지만 이것은 객체지향의 본질과는 거리가 먼 것입니다. 객체지향은 말 그대로 객체를 지향하는 것입니다. 진정한 객체지향 패러다임으로의 전환은 클래스가 아닌 객체에 초점을 맞출 때에만 얻을 수 있습니다.
    • 첫째, 어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지 고민합니다. 클래스는 공통적인 상태와 행동을 공유하는 객체들을 추상화한 것입니다. 따라서 클래스의 윤곽을 잡기 위해서는 어떤 객체들이 어떤 상태와 행동을 가지는지를 먼저 결정해야 합니다. 객체를 중심에 두는 접근 방법은 설계를 단순하고 깔끔하게 만듭니다.
    • 둘째, 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야합니다. 객체는 홀로 존재하는 것이 아닙니다. 다른 객체에게 도움을 주거나 의존하면서 살아가는 협력적인 존재입니다. 객체를 협력하는 공동체의 일원으로 바라보는 것은 설계를 유연하고 확장 가능하게 만듭니다. 객체지향적으로 생각하고 싶다면 객체를 고립된 존재로 바라보지 말고 협력에 참여하는 협력자로 바라봐야 합니다. 객체들의 모양과 윤곽이 잡히면 공통된 특성과 상태를 가진 객체들을 타입으로 분류하고 이 타입을 기반으로 클래스를 구현합니다. 훌륭한 협력이 훌륭한 객체를 낳고 훌륭한 객체가 훌륭한 클래스를 낳습니다.

도메인 구조를 따르는 프로그램 구조

  • 소프트웨어는 사용자가 원하는 어떤 문제를 해결하기 위해 만들어집니다. 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야를 도메인이라고 부릅니다.
  • 객체지향 패러다임이 강력한 이유는 요구사항을 분석하는 초기 단계부터 프로그램을 구현하는 마지막 단계까지 객체라는 동일한 추상화 기법을 사용할 수 있기 때문입니다. 요구사항과 프로그램을 객체라는 동일한 관점에서 바라볼 수 있기 때문에 도메인을 구성하는 개념들이 프로그램의 객체와 클래스로 매끄럽게 연결될 수 있습니다.
  • 자바나 C# 같은 클래스 기반의 객체지향 언어에 익숙하다면 도메인 개념들을 구현하기 위해 클래스를 사용한다는 사실이 낯설지는 않을 것입니다. 일반적으로 클래스의 이름은 대응되는 도메인 개념의 이름과 동일하거나 적어도 유사하게 지어야 합니다. 클래스 사이의 관계도 최대한 도메인 개념 사이에 맺어진 관계와 유사하게 만들어서 프로그램의 구조를 이해하고 예상하기 쉽게 만들어야 합니다.

클래스 구현하기(step1)

  • 인스턴스 변수의 가시성은 private이고 메서드의 가시성은 public 입니다. 클래스를 구현하거나 다른 개발자에 의해 개발된 클래스를 사용할 때 가장 중요한 것은 클래스의 경계를 구분 짓는 것입니다. 클래스는 내부와 외부로 구분되며 훌륭한 클래스를 설계하기 위한 핵심은 어떤 부분을 외부에 공개하고 어떤 부분을 감출지를 결정하는 것입니다. 외부에서는 객체의 속성에 직접 접근할 수 없도록 막고 적절한 public 메서드를 통해서만 내부 상태를 변경할 수 있게 해야 합니다.
  • 경계의 명확성이 객체의 자율성을 보장합니다. 또한 경계의 명확성은 프로그래머에게 구현의 자유를 제공해 줍니다.

자율적인 객체

  • 첫 번째 객체는 상태(state)와 행동(behavior)을 함께 가지는 복합적인 존재입니다. 두 번째 객체는 스스로 판단하고 행동하는 자율적인 존재 입니다.
  • 객체를 상태와 행동을 함께 포함하는 식별 가능한 단위로 정의합니다.
  • 객체지향 이전의 패러다임에서는 데이터와 기능이라는 독립적인 존재를 서로 엮어 프로그램을 구성했습니다. 이와 달리 객체지향은 객체라는 단위 안에 데이터와 기능을 한 덩어리로 묶음으로써 문제 영역의 아이디어를 적절하게 표한할 수 있게 했습니다. 데이터와 기능을 객체 내부로 함께 묶는 것을 캡슐화라고 부릅니다.
  • 대부분의 객체지향 프로그래밍 언어들은 상태와 행동을 캡슐화하는 것에서 한 걸음 더 나아가 외부에서의 접근을 통제할 수 있는 접근 제어(access control) 메커니즘도 함께 제공합니다. 많은 프로그래밍 언어들은 접근 제어를 위해 public, protected, private과 같은 접근 수정자(access modifier)를 제공합니다.
  • 객체 내부에 대한 접근을 통제하는 이유는 객체를 자율적인 존재로 만들기 위해서 입니다. 객체 내부의 대한 접근을 통제하는 이유는 객체를 자율적인 존재로 만들기 위해서입니다. 객체지향의 핵심은 스스로 상태를 관리하고, 판단하고, 행동하는 자율적인 객체들의 공통체를 구성하는 것입니다. 객체에게 원하는 것을 요청하고는 객체가 스스로 최선의 방법을 결정 할 수 있을 것이라는 점을 믿고 기다려야 합니다.
  • 캡슐화와 접근 제어는 객체를 퍼블릭 인터페이스구현으로 나눕니다. 인터페이스와 구현의 분리(separation of interface adn implementation) 원칙은 훌륭한 객체지향 프로그램을 만들기 위해 따라야 하는 핵심 원칙 입니다.
    • 퍼블릭 인터페이스 : 외부에서 접근 가능한 부분
    • 구현 : 외부에서는 접근이 불가능하고 오직 내부에서만 접근 가능한 부분
  • 일반적으로 객체의 상태는 숨기고 행동만 외부에 공개해야 합니다.

프로그래머의 자유

  • 클라이언트 프로그래머가 숨겨 놓은 부분에 마음대로 접근할 수 없도록 방지함으로써 클라이언트 프로그래머에 대한 영향을 걱정하지 않고도 내부 구현을 마음대로 변경할 수 있는 것을 구현 은닉(implementation hiding)이라고 부릅니다.
  • 접근 제어 메커니즘은 프로그래밍 언어 차원에서 클래스의 내부와 외부를 명확하게 경계 지을 수 있게 하는 동시에 클래스 작성자가 내부 구현을 은닉할 수 있게 해줍니다. 또한, 클라이언트 프로그래머가 실수로 숨겨진 부분에 접근하는 것을 막아줍니다.
  • 클라이언트 프로그래머는 내부의 구현은 무시한 채 인터페이스만 알고 있어도 클래스를 사용할 수 있기 때문에 머릿속에 담아둬야 하는 지식의 양을 줄일 수 있습니다.
  • 클래스 작성자는 언터페이스를 바꾸지 않는 한 외부에 미치는 영향을 걱정하지 않고도 내부 구현을 마음대로 변경할 수 있습니다. 다시 말해 public 영역을 변경하지 않는다면 코드를 자유롭게 수정할 수 있습니다.
  • 객체의 외부와 내부를 구분하면 클라이언트 프로그래머가 알아야 할 지식의 양이 줄어들고 클래스 작성자가 자유롭게 구현할 수 있는 폭이 넓어집니다. 따라서 클래스를 개발할 때마다 인터페이스와 구현을 깔끔하게 분리하기 위해 노력해야 합니다.
  • 설계가 필요한 이유는 변경을 관리하기 위해서 입니다. 객체지향 언어는 객체 사이의 의존성을 관리할 수 있는 기법 중에서 가장 대표적인 것이 접근 제어 입니다. 접근제어를 활용해 변경될 가능성이 있는 세부적인 구현 내용을 private 영역 안에 감춤으로써 변경으로 인한 혼란을 최소화 할 수 있습니다.

협력하는 객체들의 공동체(step2)

  • 객체지향의 장점은 객체를 이용해 도메인의 의미를 풍부하게 표현할 수있다는 것입니다. 따라서 좀 더 명시적이고 분명하게 표현할 수 있다면 객체를 사용해서 개념을 구현합니다. 그 개념이 비록 하나의 인스턴스 변수만 포함하더라도 개념을 명시적으로 표현하는 것은 전체적인 설계의 명확성을 높이는 첫걸음입니다.
  • 시스템의 어떤 기능을 구현하기 위해 객체들 사이에 이뤄지는 상호작용을 협력(Collaboration)이라고 부릅니다.
  • 객체지향 프로그램을 작성할 때는 먼저 협력의 관점에서 어떤 객체가 필요한지를 결정하고, 객체들의 공통 상태와 행위를 구현하기 위해 클래스를 작성합니다. 따라서 협력에 대한 개념을 간략하게라도 살펴보는 것이 이후의 이야기를 이해하는 데 도움이 될 것입니다.

협력에 관한 짧은 이야기

  • 객체는 다른 객체의 인터페이스에 공개된 행동을 수행하도록 요청(request)할 수 있습니다. 요청을 받은 객체는 자율적인 방법에 따라 요청을 처리한 후 응답(response) 합니다.
  • 객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 메시지를 전송(send a message)하는 것뿐입니다. 다른 객체에서 요청이 도착할 때 해당 객체가 메시지를 수신(receive a message)했다고 이야기 합니다. 메시지를 수신한 객체는 스스로의 결정에 따라 자율적으로 메시지를 처리할 방법을 결정합니다. 이처럼 수신된 메시지를 처리하기 위한 자신만의 방법을 메서드(method)라고 부릅니다.
  • 메시지와 메서드를 구분하는 것은 매우 중요합니다. 객체지향 패러다임이 유연하고, 확장 가능하며, 재사용 가능한 설계를 낳는다는 명성을 얻게 된 배경에는 메시지와 메서드를 명확하게 구분하는 것도 단단히 한몫합니다. 메시지와 메서드의 구분에서부터 다형성(polymorphism)의 개념이 출발합니다.

03. 할인 요금 구하기

할인 요금 계산을 위한 협력 시작하기(step3)

  • 추상화(abstraction라는 원리를 바탕으로 한 상속(inheritance)다형성이 중요합니다.

할인 정책과 할인 조건(step4)

  • 부모 클래스에 기본적인 알고리즘의 흐름을 구현하고 중간에 필요한 처리를 자식 클래스에게 위임하는 디자인 패턴을 TEMPLATE METHOD 패턴이라고 부릅니다.

오버라이딩과 오버로딩

  • 오버라이딩은 부모 클래스에 정의된 같은 이름, 같은 파라미터 목록을 가진 메서드를 자식 클래스에서 재정의하는 경우를 가리킵니다. 자식 클래스의 메서드는 오버라이딩한 부모 클래스의 메서드를 가리기 때문에 외부에서는 부모 클래스의 메서드가 보이지 않습니다.
  • 오버로딩은 메서드의 이름은 같지만 제공되는 파라미터의 목록이 다릅니다. 오버로딩한 메서드는 원래의 메서드를 가리지 않기 때문에 이 메서드들은 사이 좋게 공존합니다.

할인 정책 구성하기

  • 생성자의 파라미터 목록을 이용해 초기화에 필요한 정보를 전달하도록 강제하면 올바른 상태를 가진 객체의 생성을 보장할 수 있습니다.

04. 상속과 다형성

컴파일 시간 의존성과 실행 시간 의존성

  • 어떤 클래스가 다른 클래스에 접근할 수 있는 경로를 가지거나 해당 클래스의 객체의 메서드를 호출할 경우 두 클래스 사이의 의존성이 존재한다고 합니다.
  • 코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있습니다. 클래스 사이의 의존성과 객체 사이의 의존성은 동일하지 않을 수 있습니다. 그리고 유연하고, 쉽게 재사용할 수 있으며, 확장 가능한 객체지향 설계가 가지는 특징은 코드의 의존성과 실행 시점의 의존성이 다르다는 것입니다.
  • 코드의 의존성과 실행 시점의 의존성이 다르면 다를수록 코드를 이해하기 어려워집니다. 반면 코드의 의존성과 실행 시점의 의존성이 다르면 다를수록 코드는 더 유연해지고 확장 가능해집니다. 이와 같은 의존성의 양면성은 설계가 트레이드오프의 산물이라는 사실을 잘 보여줍니다.
  • 설계가 유연해질수록 코드를 이해하고 디버깅하기는 점점 더 어려워집니다. 유연성을 억제하면 코드를 이해하고 디버깅하기는 쉬워지지만 재사용성과 확장 가능서은 낮아집니다. 객체지향 설계자는 항상 유연성과 가독성 사이에서 고민해야 합니다. 무조건 유연한 설계도, 무조건 읽기 쉬운 코드도 정답이 아닙니다.

차이에 의한 프로그래밍

  • 상속은 객체지향에서 코드를 재사용하기 위해 가장 널리 사용되는 방법입니다. 상속을 이용하면 클래스 사이에 관계를 설정하는 것만으로 기존 클래스가 가지고 있는 모든 속성과 행동을 새로운 클래스에 포함시킬 수 있습니다.
  • 상속은 기존 클래스를 기반으로 새로운 클래스를 쉽고 빠르게 추가할 수 있는 간편한 방법을 제공합니다. 또한 상속을 이용하면 부모 클래스의 구현은 공유하면서도 행동이 다른 자식 클래스를 쉽게 추가할 수 있습니다.
  • 부모 클래스와 다른 부분만 추가해서 새로운 클래스를 쉽고 빠르게 만드는 방법을 차이에 의한 프로그래밍(programming by difference)이리고 부릅니다.

자식 클래스와 부모 클래스

  • 상속은 두 클래스 사이의 관계를 정의하는 방법입니다.
  • 상속 관계를 선언함으로써 한 클래스는 자동으로 다른 클래스가 제공하는 코드를 자신의 일부로 합칠 수 있습니다. 따라서 상속을 사용하면 코드 중복을 제거하고 여러 클래스 사이에서 동일한 코드를 공유할 수 있게 됩니다.
  • 코드를 제공하는 클래스를 슈퍼클래스(superclass), 부모 클래스(parent class), 부모(parent), 직계조상(immediate ancestor), 직접적인 조상(direct ancestor)이라고 부릅니다.
  • 코드를 제공받는 클래스를 서브클래스(subclass), 자식 클래스(child class), 자식(child), 직계 자손(immediate descendant), 직접적인 자손(direct descendant)이라고 부릅니다.
  • 상속 계층에서 특정 클래스보다 상위에 위치한 모든 클래스를 조상(ancestors)이라고 부르고, 특정 클래스보다 하위에 위치한 모든 클래스 자손(descendants)이라고 부릅니다.
  • 상속에 참여하는 두 클래스를 가리키는 가장 일반적인 용어는 슈퍼클래스(superclass)서브클래스(subclass)입니다.
    • C++에는 기반 클래스(base class)와 파생 클래스(derived class)라는 새로운 용어를 도입하기도 했습니다.
  • 클래스 관계는 상대적입니다. 어떤 클래스를 기준으로 하느냐에 따라 상속 관계에 참여하는 클래스의 역할이 달라집니다.

상속과 인터페이스

  • 상속이 가치 있는 이유는 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문입니다. 이것은 상속을 바라보는 일반적인 인식과는 거리가 있는데 대부분의 사람들은 상속의 목적이 메서드나 인스턴스 변수를 재사용하는 것이라고 생각합니다.
  • 인터페이스는 객체가 이해할 수 있는 메시지의 목록을 정의합니다. 상속을 통해 자식 클래스는 자신의 인터페이스에 부모 클래스의 인터페이스를 포함하게 됩니다. 결과적으로 자식 클래스는 부모 클래스가 수신할 수 있는 모든 메시지를 수신할 수 있기 때문에 외부 객체는 자식 클래스를 부모 클래스와 동일한 타입으로 간주할 수 있습니다. 자식 클래스는 상속을 통해 부모 클래스의 인스턴스를 물려받기 때문에 부모 클래스 대신 사용될 수 있습니다. 컴파일러는 코드 상에서 부모 클래스가 나오는 모든 장소에서 자식 클래스를 사용하는 것을 허용합니다.
  • 자식 클래스가 부모 클래스를 대신하는 것을 업캐스팅(upcasting)이라고 부릅니다. 업캐스팅이라고 부르는 이유는 일반적으로 클래스 다이어그램을 작성할 때 부모 클래스를 자식 클래스 위에 위치 시키기 때문입니다. 아래에 위치한 자식 클래스가 위에 위치한 부모 클래스로 자동적으로 타입 캐스팅되는 것처럼 보이기 때문에 업캐스팅이라는 용어를 사용합니다.

다형성

  • 메시지와 메서드는 다른 개념입니다.
  • 동일한 메시지를 전송하지만 실제로 어떤 메서드가 실행될 것인지는 메시지를 수신하는 객체의 클래스가 무엇이냐에 따라 달라집니다. 이를 다형성이라고 부릅니다.
  • 다형성은 컴파일 시간 의존성과 실행 시간 의존성을 다르게 만들 수 있는 객체지향의 특성을 이용해 서로 다른 메서드를 실행할 수 있게 합니다.
  • 다형성이란 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력을 의미합니다. 따라서 다형적인 협력에 참여하는 객체들은 모두 같은 메시지를 이해할 수 있어야 합니다. 다시 말해 인터페이스가 동일해야 한다는 것입니다.
    • 인터페이스를 통일하기 위한 구현 방법이 상속입니다.
  • 다형성을 구현하는 방법은 매우 다양하지만 메시지에 응답하기 위해 실행될 메서드를 컴파일 시점이 아닌 실행 시점에 결정한다는 공통점이 있습니다. 다시 말해 메시지와 메서드를 실행 시점에 바인딩 한다는 것입니다. 이를 지연 바인딩(lazy binding 또는 동적 바인딩(dynamic binding이라고 부릅니다.
  • 전통적인 함수 호출처럼 컴파일 시점에 실행될 함수나 프로시저를 결정하는 것을 초기 바인딩(early binding) 또는 정적 바인딩(static binding)이라고 부릅니다.
  • 객체지향이 컴파일 시점의 의존성과 실행 시점의 의존성을 분이하고, 하나의 메시지를 선택적으로 서로 다른 메서드에 연결할 수 있는 이유가 바로 지연 바딩이라는 메커니즘을 사용하기 때문입니다.
  • 상속을 이용하면 동일한 인터페이스를 공유하는 클래스들을 하나의 타입 계층으로 묶을 수 있습니다. 이런 이유로 대부분의 사람들은 다형성을 이야기할 때 상속을 함께 언급합니다. 그러나 클래스를 상속받는 것만이 다형성을 구현할 수 있는 유일한 방법은 아닙니다.

구현 상속과 인터페이스 상속

  • 상속을 구현 상속(implementation inheritance)인터페이스 상속(interface inheritance)으로 분류 할 수 있습니다.
  • 흔히 구현 상속을 서브클래싱(subclassing)이라고 부르는 인터페이스 상속을 서브타이핑(subtyping)이라고 부릅니다.
  • 순수하게 코들르 재사용하기 위한 목적으로 상속을 사용하는 것을 구현 상속이라고 부릅니다.
  • 다향적인 협력을 위한 부모 클래스와 자식 클래스가 인터페이스를 공유할 수 있도록 상속을 이용하는 것을 인터페이스 상속이라고 부릅니다.
  • 상속은 구현 상속이 아니라 인터페이스 상속을 위해 사용해야 합니다. 대부분의 사람들은 코드 재사용을 상속의 주된 목적이라고 생각하지만 이것은 오해 입니다. 인터페이스를 재사용할 목적이 아니라 구현을 재사용할 목적으로 상속을 사용하면 변경에 취약한 코드를 낳게 될 확률이 높습니다.

인터페이스와 다형성

  • 종종 구현은 공유할 필요가 없고 순수하게 인터페이스만 공유하고 싶을 때가 있습니다. 이를 위해 인터페이스라는 프로그래밍 요소를 제공합니다.

05. 추상화와 유연성

추상화의 힘

  • 같은 계층에 속하는 클래스들이 공통으로 가질 수 있는 인터페이스를 정의하며 구현의 일부(추상 클래스인 경우) 또는 전체(자바 인터페이스인 경우)를 자식 클래스가 결정할 수 있도록 결정권을 위임합니다.
  • 추상화를 할 경우 장점
    • 첫 번째 장점은 추상화의 계층만 따로 떼어 놓고 살펴보면 요구사항의 정책을 높은 수준에서 서술할 수 있습니다.
    • 두 번째 장점은 추상화를 이용하면 설계가 좀 더 유연해진다는 것입니다.
  • 추상화를 사용하면 세부적인 내용을 무시한 채 상위 정책을 쉽고 간단하게 표현할 수 있습니다. 추상화의 이런 특징은 세부사항에 억눌리지 않고 상위 개념만으로도 도메인의 중요한 개념을 설명 할 수 있게 합니다. 추상화를 이용한 설계는 필요에 따라 표현의 수준을 조정하는 것을 가능하게 해줍니다.
  • 추상화를 이용해 상위 정책을 기술한다는 것은 기본적인 애플리케이션의 협력 흐름을 기술한다는 것을 의미합니다. 재사용 가능한 설계의 기본을 이루는 디자인 패턴(design pattern)이나 프레임워크(framework) 모두 추상화를 이용해 상위 정책을 정의하는 객체지향의 메커니즘을 활용하고 있습니다.
  • 추상화를 이용해 상위 정책을 표현하면 기존 구조를 수정하지 않고도 새로운 기능을 쉽게 추가하고 확장할 수 있습니다. 다시 말해 설계를 유연하게 만들 수 있습니다.

유연한 설계(step5)

  • 책임의 위치를 결정하기 위해 조건문을 사용하는 것은 협력의 설계 측면ㄴ에서 대부분의 경우 좋지 않은 선택입니다. 항상 예외 케이스를 최소화하고 일관성을 유지할 수 있는 방법을 선택해야 합니다.
  • 추상화를 중심으로 코드의 구조를 설계하면 유연하고 확장 가능한 설계를 만들 수 있습니다.
  • 추상화가 유연한 설계를 가능하게 하는 이유는 설계가 구체적인 상황에 결합되는 것을 방지하기 때문입니다.
  • 컨텍스트 독립성(context independency)이라고 불리는 개념이 프레임워크와 같은 유연한 설계가 필수적인 분야에거 그 진가를 발휘합니다.
  • 유연성이 필요한 곳에 추상화를 사용합니다.

추상 클래스와 인터페이스 트레이드오프(step6)

  • 이상적으로는 인터페이스를 사용하도록 변경한 설계가 더 좋을 것입니다. 하지만 현실적으로는 인터페이스를 추가하는 것이 과하다는 생각이 들 수도 있습니다. 여기서 이야기하고 싶은 사실은 구현과 관련된 모든 것들이 트레이드오프의 대상이 될 수 있다는 사실입니다. 여러분이 작성하는 모든 코드에는 합당한 이유가 있어야 하빈다. 비록 아주 사소한 결정이더라도 트레이드오프를 통해 얻어진 결론과 그렇지 않은 결론 사이의 차이는 큽니다. 고민하고 트레이드오프 해야 합니다.

코드 재사용

  • 상속은 코드를 재사용하기 위해 널리 사용되는 방법입니다. 그러나 널리 사용되는 방법이라고 해서 가장 좋은 방법인 것은 아닙니다.
  • 객체지향 설계와 관련된 자료를 조금이라도 본 사람들은 코드 재사용을 위해서는 상속보다는 합성(composition)이 더 좋은 방법이라는 이야기를 많이 들었을 것입니다. 합성은 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 재사용하는 방법을 말합니다.
  • 많은 사람들은 상속 대신 합성을 선호 합니다.

상속

  • 상속은 객체지향에서 코드를 재사용하기 위해 널리 사용되는 기법입니다. 하지만 두 가지 관점에서 설계에 안 좋은 영향을 미칩니다. 하나는 상속이 캡슐화를 위반한다는 것이고, 다른 하나는 설계를 유연하지 못하게 만든다는 것입니다.
  • 상속의 가장 큰 문제점은 캡슐화를 위반 한다는 것입니다. 상속을 이용하기 위해서는 부모 클래스의 내부 구조를 잘 알고 있어야 합니다. 결과적으로 부모 클래스의 구현이 자식 클래스에게 노출되기 때문에 캡슐화가 약화됩니다. 캡슐화의 약화는 자식 클래스가 부모 클래스에 강하게 결합되도록 만들기 때문에 부모 클래스를 변경할 때 자식 클래스도 함께 변경될 확률을 높입니다. 결과적르로 상속을 과도하게 사용한 코드는 변경하기도 어려워집니다.
  • 상속의 두 번째 단점은 설계가 유연하지 않다는 것입니다. 상속은 부모 클래스와 자식 클래스 사이의 관계를 컴파일 시점에 결정합니다. 따라서 실행 시점에 객체의 종류를 변경하는 것이 불가능합니다.

합성

  • 상속과 다른 점은 상속이 부모 클래스의 코드와 자식 클래스의 코드를 컴파일 시점에 하나의 단위로 강하게 결합하는 데 비해 인터페이스를 통해 약하게 결합 됩니다. 메서드를 제공한다는 사실만 알고 내부 구현에 대해서는 전혀 알지 못합니다. 인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법을 합성이라고 부릅니다.
  • 합성은 상속이 가지는 두 가지 문제점을 모두 해결합니다. 인터페이스에 정의된 메시지를 통해서만 재사용이 가능하기 때문에 구현을 효과적으로 캡슐화할 수 있습니다. 또한 의존하는 인스턴스를 교체하는 것이 비교적 쉽기 때문에 설계를 유연하게 만듭니다. 상속은 클래스를 통해 강하게 결합되는 데 비해 합성은 메시지를 통해 느슨하게 결합됩니다. 따라서 코드 재사용을 위해서는 상속보다는 합성을 선호하는 것이 더 좋은 방법입니다.
  • 대부분의 설계에서는 상속과 합성을 함께 사용해야 합니다. 코드를 재사용하는 경우에는 상속보다 합성을 선호하는 것이 옳지만 다형성을 위해 인터페이스를 재사용하는 경우에는 상속과 합성을 함께 조합해서 사용할 수 밖에 없습니다.
  • 객체지향이란 객체를 자향하는 것입니다. 따라서 객체지향 패러다임의 중심에는 객체가 위치합니다. 그러나 각 객체를 따로 떼어 놓고 이야기하는 것은 무의미합니다. 객체지향에서 가장 중요한 것은 애플리케이션의 기능을 구현하기 위해 협력에 참여하는 객치들 사이의 상호작용입니다. 객체들은 협력에 참여하기 위해 역할을 부여받고 역할에 적합한 책임을 수행합니다.
  • 객체지향 설계의 핵심은 적절한 협력을 식별하고 협력에 필요하 역할을 정의한 후에 역할을 수행할 수 있는 적절한 객체에게 적절한 책임을 할당하는 것입니다.

참고