본문 바로가기

Programming

객체지향의 사실과 오해 (The Essence of Object-Orientation) - 책정리

객체지향의 사실과 오해 (The Essence of Object-Orientation)

역할, 책임, 협력 관점에서 본 객체지향

Roles, Responsibilities, and Collaborations


1. 협력하는 객체들의 공동체

  • 역할, 책임, 협력
  • 역할은 기대되는 책임이 있고, 각 역할들이 협력해서 원하는 것들을 해낸다.
    1. 동일한 역할 중복 가능
    2. 역할은 대체 가능함
    3. 책임을 수행하는 방법은 선택 가능 (동일 요청에 다른 방식 응답 - 다형성)
    4. 하나가 여러 역할 수행 가능
  • 객체의 덕목 2가지: 협력적, 자율적
  • 협력은 메시지를 통해 이루어진다.
  • 요청을 표현하는 메시지와, 요청을 처리하는 구체적 방법인 메서드를 분리하는 것은 자율성을 높이는데에 도움이 되고, 캡슐화(encapsulation)와 관련이 깊다.
  • 객체지향은 클래스들의 정적인 관계나 구조, 메서드가 아니라 객체의 역할, 책임, 협력 에 집중해야 한다.

2. 이상한 나라의 객체

  • 객체지향 패러다임의 목적은 현실 세계 모방이 아니라, 현실 세계를 기반으로 새로운 세계를 창조하는 것
  • 객체는 상태(state), 행동(behavior), 식별자(identifier) 를 갖는다.
  • 객체의 정의: 식별 가능한 개체 또는 사물. 구체적인 사물일 수도 있고, 시간처럼 추상적잉ㄴ 개념일 수도 있다. 객체는 구별 가능한 식별자, 특징적인 행동, 변경 가능한 상태를 가진다. 소프트웨어 안에서 객체는 저장된 상태와 실행 가능한 코드를 통해 구현된다.
  • 상태
    • 상태를 이용해서 과거의 동작을 생각하지 않고 현재를 기반으로 객체의 행동 방식을 이해한다. 복잡성을 완화하고 인지 과부하를 줄여주는 개념이 된다.
    • 상태는 스스로의 행동에 의해서만 변경되는 것을 보장한다 -> 객체의 자율성을 유지한다.
  • 행동
    • 객체의 행동은 상태에 영향을 받는다. (상호작용이 현재의 상태에 어떤 방식으로 의존하는가)
    • 객체의 행동은 상태를 변경시킨다. (상호작용이 어떻게 현재의 상태를 변경시키는가)
    • 행동이란 외부의 요청 또는 수신 메시지에 응답하기 위해 동작하고 반응하는 활동.
    • 행동은 객체 자신의 상태를 변경하거나, 협력하는 다른 객체에 메시지를 전달할 수 있다.
  • 객체의 행동을 유발하는 것은 외부에서 온 메시지이지만, 객체의 상태를 변경하는 여부는 객체 스스로 정한다. -> 객체의 자율성, 지능이 높아진다 -> 협력은 유연하고 간결해진다.
  • 식별자
    • 불변한 값들은 동등성을 확인 (equality)
    • 가변의 값들은 동일성을 확인 (identical)
    • 객체는 어떤 상태에 있어라도 유일하게 식별되도록 한다.
  • 객체를 기계로 바라보는 관점은 상태/행동/식별자 에 대한 이해를 높이고, 캡슐화와 메시지를 통한 협력 관계를 잘 설명한다.
  • 좋은 객체를 만들기 위해서는 상태보다는 행동에 우선 초점을 맞추어야 한다.
    -> 애플리케이션에 필요한 협력을 생각하고, 그에 필요한 행동을 생각하고, 행동 수행 객체를 선택한다.
  • “행동이 상태를 결정한다.”
  • 객체지향은 현실 세계의 단순 모방, 추상화 가 아니고 특성이 전혀 다르다.
    • 현실의 수동적인 것들이 소프트웨어 객체로 구현될 때는 능동적으로 변한다. (의인화)
    • 현실 속 객체의 의미 일부가 소프트웨어 객체로 전달 된다. (은유)
  • 이상한 나라를 창조하라.

3. 타입과 추상화

  • 지하철노선도 추상화의 예시: 불필요한 사실관계 정보를 버리고, 필요한 것에 집중하여 추상화한다.
  • 추상화: 어떠한 것을 더 명확하게 이해하기 위해서 부분을 의도적으로 생략하거나 감춤 -> 복잡도 극복
    1. 구체적 사물들 간의 공통점을 취하고 차이점을 버려서 일반화 한다.
    2. 중요한 부분을 강조하기 위해 불필요한 세부 사항들을 제거하여 단순화 한다.
  • 객체는 특정 개념이 적용 가능한 구체적인 사물 -> 개념이 객체에 적용되면 객체를 개념의 인스턴스라 할 수 있다.
  • 객체 추상화 시에 객체 분류 방식으로 쓰이는 개념(concept)의 3가지 관점
    • 심볼: 개념의 명칭이나 이름 (ex. 트럼프)
    • 내연: 개념의 완전한 정의 (ex. 몸이 납작, 두손 두발이 네모 귀퉁이에)
    • 외연: 개념에 속하는 모든 객체의 집합 set (ex. 정원사, 병사, 왕, 여왕 etc)
  • 타입: 객체들을 묶기 위한 틀 (객체 분류 방식으로서의 개념과 완전히 동일한 정의)
    • 데이터가 어떻게 사용되느냐에 관한 것 (어떤 데이터에 어떤 연산자?)
    • 타입에 속한 데이터를 메모리에 어떻게 표현하는지는 모른다.
    • 타입은 데이터를 표현할 수 있다.
  • 타입은 데이터가 아니라 행동에 따라서 결정된다. (같은 타입은 동일한 행동)
    • 데이터가 달라도, 동일한 메시지를 수신하고 처리한다 (다형성)
    • 데이터 내부 표현방식과 무관하고 행동만이 고려대상이다. -> 외부에 데이터를 감춰야 한다. (캡슐화)
  • 적절한 순서
    • 객체가 외부에 제공해야하는 행동을 먼저 생각한다.
    • 그 책임을 수행하는 데 적합한 데이터를 나중에 결정한다.
    • 데이터를 책임을 수행하는 데 필요한 외부 인터페이스 뒤로 캡슐화 한다.
  • 타입과 타입 사이의 일반화/특수화 (Generalization/Specialization)
    • 포괄적인 내용으로 범위가 커지면 일반화
    • 세부적인 내용으로 범위가 좁아지면 특수화
    • 중요한 것은 일반화/특수화 관계를 결정하는 것이 상태 데이터가 아니라 행동 이라는 것
    • 일반적인 타입은 특수한 타입에 비해 더 적은 수의 행동을 갖는다.
    • 일반적인 타입(supertype), 특수한 타입(subtype)
  • 타입의 목적
    • 타입은, 시간에 따라 동적으로 변하는 객체의 상태를 시간과 무관한 정적인 모습으로 다룰 수 있게 해준다.
    • 타입은 추상화. 객체의 동적인 특성을 추상화한다. 시간에 따른 상태 변경이라는 복잡성을 단순화 한다.
  • 객체를 분류하는 기준은 타입.
  • 타입을 나누는 기준은 객체가 수행하는 행동.
  • 객체를 분류하기 위해 타입을 정하고, 타입을 구현하는 방법 중 하나가 클래스.

4. 역할, 책임, 협력

  • 객체지향 설계의 품질을 결정하는 것은 역할, 책임, 협력 -> 객체들 사이의 협력의 품질이 가장 중요하다.
  • 객체와 책임을 설계하고, 그 다음에 구현을 한다.
  • 객체의 책임 분류
    • 하는 것 (외부에서 접근 가능한 공용 서비스)
      • 객체를 생성하거나 계산을 하는 등의 스스로 하는 것
      • 다른 객체의 행동을 시작시키는 것
      • 다른 객체의 활동을 제어하고 조절하는 것
    • 아는 것 (외부에 제공해 줄 수 있는 정보)
      • 개인적인 정보에 관해 아는 것
      • 관련된 객체에 관해 아는 것
      • 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것
  • 책임은 즉 공용 인터페이스를 구성한다. -> 캡슐화로 연결된다.
  • 무엇을 할 수 있는지에 대한 나열: 책임
  • 협력에 참여하는 두 객체 사이의 관계: 메시지
  • 동일한 메시지에 동일한 책임을 수행할 수 있으면 -> 역할로 묶는다.
    • 역할의 개념을 사용하면 유사한 협력을 추상화해서 인지 과부하를 줄인다.
    • 다양한 객체들이 동일한 협력에 참여 -> 재사용성 높아진다.
    • 역할은, 단순성, 유연성, 재사용성 을 뒷받침하는 핵심 개념
  • 객체지향의 핵심은 객체가 협력 안에서 어떤 책임과 역할을 수행할 것인지를 결정하는 것
  • 협력이라는 문맥에서 책임(행동)을 결정한다. -> 그 다음 행동 수행을 위한 데이터를 고민한다.
    -> 그 다음에 클래스 구현 방법을 정한다.
  • 객체지향 설계 기법 3가지
    1. 책임주도개발 (Responsibility-Driven Development)
      • 시스템 책임을 객체 책임으로 변환, 객체의 책임과 상호작용에 집중 (역할, 책임, 협력)
    2. 디자인 패턴
      • 책임주도설계의 결과물이면서 지름길이 될 수 있다.
    3. 테스트주도개발 (Test-Driven Development)
      • 책임주도설계의 기본 개념, 다양한 원칙, 연습, 패턴을 종합적으로 이해해야함

5. 책임과 메시지

  • 명확한 책임과 역할을 가진 객체들이 협력에 참여해야 한다. (실험 예시)
  • 자율적인 책임: 너무 세세하지 않고, 너무 추상적이지 않은, 문맥에 적합한 책임.
    • 어떻게(How)가 아니라 무엇(What)을
  • 메시지는 객체들이 서로 협력하기 위해 사용할 수 있는 유일한 의사소통 수단
    • 객체가 메시지를 수신할 수 있다는 것은 객체가 메시지에 해당하는 책임을 수행할 수 있다는 것
    • 객체는 메시지를 처리하기 위한 방법을 자율적으로 선택함 (내외부 분리)
  • 메서드: 메시지를 처리하기 위해 내부적으로 선택하는 방법
    • 메시지 수신 객체가 런타임에 메서드를 선택하는 것은 객체지향 언어의 핵심 특징 중 하나
  • 다형성
    • 서로 다른 다입에 속하는 객체들이 동일한 메시지에 대해 서로 다른 메서드를 이용해 메시지를 처리하는 메커니즘
    • 메시지는 '무엇'은 명시하고, '어떻게'는 수신자가 결정한다.
    • 역할, 책임, 협력 과 깊은 관련이 있음
    • 객체들의 대체 가능성을 이용해 설계를 유연하고 재사용 가능하게 한다.
    • 수신자의 종류를 캡슐화 하는 것.
  • 유연하고 확장 가능하고 재사용성이 높은 협력의 의미
    • 협력이 유연: 수신자가 바뀌어도 송신자에 파급효과 없이 유연하게 협력 변경 가능
    • 협력 수행 방식을 확장 가능: 세부적인 협력의 수행방식을 쉽게 수정 가능하다.
    • 협력이 수행되는 방식 재사용 가능: 다양한 문맥에서 협력을 재사용 가능하다.
  • 객체지향의 핵심: 메시지
  • 협력과 문맥을 먼저 고려하고, 객체 내부의 데이터 구조를 정한다.
    • 협력이라는 문맥 안에서 독립적인 객체를 생각한다.
  • 메시지를 중심으로 협력을 설계하고, 메시지가 객체를 선택하게 해야 한다.
    • 협력이라는 문맥 안에서 필요한 메시지를 결정한 후, 메시지를 수신할 객체를 선택한다.
  • RDD (Responsibility-Driven Development)
    • What/Who 사이클: 어떤 행위가 필요한지를 먼저 결정하고, 수행할 객체를 결정한다.
  • 인터페이스: 객체가 수신할 수 있는 메시지 목록
    • 내부가 바뀌어도 인터페이스가 같으면 상관없다.
  • 책임, 메시지, 인터페이스
    • 객체의 책임이 자율적이어야 함
    • 메시지: 객체간 요청 전송의 메커니즘
    • 객체의 인터페이스는 객체가 수신 가능한 메시지 목록으로 채워짐
    • 메서드: 메시지 수신 시 책임을 수행하는 방법
    • 인터페이스: 객체가 책임을 수행하기 위해 외부로부터 메시지 받는 통로
  • 인터페이스의 외부와 실제 내부 구현을 분리한다. (캡슐화, 정보은닉)
  • 캡슐화의 2가지 관점
    • 상태와 행위의 캡슐화 (데이터와 프로세스를 객체로 묶는다)
    • 사적인 비밀의 캡슐화 (변경이 빈번한 비밀을 안정적인 인터페이스 뒤로 숨긴다)
  • 객체를 자율적으로 바라보는 것 -> 객체의 내부와 외부를 분리하는 것
  • 객체의 책임이 자율적이어야 하는 이유
    1. 자율적인 책임은 협력을 단순하게 만든다. (책임의 적절한 추상화)
    2. 자율적인 책임은 내/외부를 명확하게 분리한다.
    3. 자율적인 책임은 내부가 변경되어도 외부에 영향이 없다. (캡슐화 -> 낮은 결합도)
    4. 자율적인 책임은 협력의 대상이 다양해질 수 있는 유연성을 제공한다.
    5. 자율적인 책임은 객체의 역할을 이해하기 쉽게 해준다.
    • 즉, 적절한 추상화, 높은 응집도, 낮은 결합도, 캡슐화, 인터페이스와 구현 분리, 유연성과 재사용성 향상
  • 메시지를 통해서 객체들의 책임을 자율적으로 만든다.

6. 객체 지도

  • 기능기반 보다는 구조기반 모델이 범용성이 좋고 요구사항 변경에 안정적이다.
    • 자주 변경되는 기능이 아니라, 안정적인 구조를 따라 역할, 책임, 협력을 구성한다.
  • 도메인 모델: 이란 사용자들이 도메인을 바라보는 관점, 설계자가 시스템 구조를 바라보는 관점, 동시에 소프트웨어 구현 그 자체
    • 객체지향 패러다임이 도메인 구조화를 하게 도와줌
    • 도메인 모델을 은유를 통해서 소프트웨어 객체로 투영한다.
    • 도메인 모델은 사용자가 도메인을 보는 관점을 반영해야 본질적인 측면이 잘 나타난다.
      • 본질적인 개념과 규칙이 기반되어야, 변경에 쉽게 대처할 가능성이 커진다.
      • 즉, 안정적인 구조를 제공한다.
  • 유즈케이스 (Use-Case)
    • 사용자와 시스템 간의 상호작용을 보여주는 텍스트
    • 하나의 시나리오가 아니라 여러 시나리오들의 집합
    • 단순한 피처 목록이 아니고, 이야기를 통해 연관된 기능들을 묶어서 보여준다.
    • 사용자 인터페이스와 관련된 세부 정보를 포함하지 않는다.
    • 내부 설계와 관련된 정보를 포함하지 않는다.
  • 도메인 모델은 안정적인 구조를 개념화해주고, 유즈케이스는 불안정한 기능을 서술한다. (일반적으로 쓰이는 도구들)
  • 견고한 객체지향 애플리케이션 개발
    1. 사용자 관점에서 시스템의 기능을 명시
    2. 사용자와 설계자가 공유하는 안정적인 구조 기반으로 기능을 책임으로 변환
  • 도메인 모델은 문서나 다이어그램이 아니고, 사람들의 머릿속에 공유된 멘탈 모델이다.
    • 동일한 용어와 동일한 개념을 이용해 의사소통하고, 코드로부터 도메인 모델을 유추하게 하는 것이 실제 목표
  • 안정적인 도메인 모델을 기반으로 기스템의 기능을 구현
  • 도메인 모델과 코드를 밀접하게 연관하도록 한다.

-> 유지보수하기 쉽고 유연한 객체지향 시스템

7. 함께 모으기

  • 객체지향 설계 안의 3가지 상호 연관된 관점이 있다. 구현 클래스를 이러한 관점들로 바라보는 것을 이해한다.
    1. 개념 관점 (Conceptual Perspective): 도메인 안의 개념과 개념들 사이의 관계 표현
    2. 명세 관점 (Specification Perspective): 도메인이 아니라 소프트웨어 안에 살아있는 객체들의 책임(객체의 인터페이스)에 초점
    3. 구현 관점 (Implementation Perspective): 실제 작업 수행하는 코드, 객체들의 책임을 수행하는데 필요한 동작(속성, 메서드)하는 코드 작성에 초점
  • 역할, 책임, 협력을 이용해 객체 인터페이스를 식별하는 방식
    1. 협력에 참여하기 위해 객체가 수신해야 하는 메시지 결정 (명세 관점, 객체 인터페이스)
    2. 메시지들이 모여 객체 인터페이스를 구성
  • 커피집 예시
    • 도메인 문맥 정리 (예, 커피집)
    • 협력 찾기 (예, 커피를 주문)
    • 인터페이스 정리 (메시지 정리)
    • 구현 (코드)
  • 구상한 설계는 코드로 구현하면서 변경됨 -> 코드를 빨리 구현해보고 피드백을 얻으면서 설계를 수정한다.
  • 객체가 어떤 책임을 수행해야 하는지 결정한 후에 책임을 수행하는데 필요한 객체 속성을 결정한다.
    -> 객체의 구현 세부사항을 객체 공용 인터페이스에 노출시키지 않고 구현과 분리하는 기본적인 방법
  • UML 자유 수정 가능. UML은 의사소통을 위한 표기법이고, 꼭 지켜야하는 법칙은 아님.
  • 객체지향 코드에서 클래스 안에 3가지 관점을 모두 각 포함하면서 관점에 대응하는 요소들을 명확하고 깔끔하게 나타내야한다.
    • 개념관점: 도메인 개념을 클래스에 최대한 반영한다.
    • 명세관점: 클래스의 공용인터페이스는 구현과 관련 세부 사항이 드러나지 않도록 한다.
    • 구현관점: 클래스 내부 구현은 철저히 캡슐화 한다.
  • 도메인 개념을 참조하고, 인터페이스와 구현을 분리하라.