오브젝트 | Chapter 01. 객체, 설계

  • 실무가 어느 정도 발전한 후에야 실무의 실용성을 입증할 수 있는 이론이 모습을 갖춰감
  • 해당 분야가 충분히 성숙해지는 시점에야 이론이 실무를 추월하게 됨
  • 어떤 분야든 초기 단계에서는 아무것도 없이 이론을 정립하는 것보다 실무의 관찰 결과를 바탕으로 이론을 정립하는 것이 최선
  • 훌륭한 설계

    • 대부분으 설계 원칙 및 개념 역시 이론에서 출발해 실무에 스며들기보다 실무에서 반복적으로 적용되던 기법을 이론화한 것이 대부분
    • 소프트웨어의 규모가 커질수록 소프트웨어 설계 분야에서 이론이 실무를 추월할 가능성은 희박함
  • 설계, 유지보수를 이야기할 때 이론을 중심에 두는 것은 적절하지 않음

    • 설계 분야에서 실무는 이론을 압도함
    • 설계에 관해 설명할 때 가장 유용한 도구는 개념/용어보다 코드 그 자체
  • 개발자는 구체적인 코드를 만지며 손을 더럽힐 때 가장 많을 것을 얻어가는 존재

01. 티켓 판매 애플리케이션 구현하기

  • 소극장

    • 관람객의 가방 안에 초대장이 있는지 확인함
    • 초대장이 있다면 티켓을 관람객의 가방에 넣어주고 없다면 티켓을 판매함

      • 관람객의 가방에서 티켓 금액만큼 차감후 매표소 금액을 증가시킴
      • 관람객의 가방에 티켓을 넣어줌

02. 무엇이 문제인가

  • 모듈

    • 크기와 상관 없이 클래스나 패키지, 라이브러리와 같이 프로그램을 구성하는 임의의 요소

소프트웨어 모듈이 가져야 하는 세 가지 기능

로버트 마틴

  • 모든 소프트웨어 모듈에는 세 가지 목적이 있음

    1. 실행 중에 제대로 동작하는 것
    2. 변경을 위해 존재하는 것
    3. 코드를 읽는 사람과 의사소통하는 것

    ⇒ 모든 모듈은 제대로 실행돼야 하고, 변경이 용이해야 하며, 이해하기 쉬워야 함

예상을 빗나가는 코드

  • 제3자인 소극장이 관람객의 가방을 마음대로 열어보는 것이 문제
  • 이해 가능한 코드 : 그 동작이 예상에서 크게 벗어나지 않는 코드
  • 현재의 코드 : 상식과 너무 다르게 동작해 코드를 읽는 사람과 제대로 의사소통하지 못함

변경에 취약한 코드

  • Theater : 관람객이 가방을 가지며 판매원이 매표소에서만 티켓을 판매한다는 지나치게 세부적인 사실에 의존함

    • 세부적인 사실 중 한 가지라도 바귀면 해당 클래스와 이 클래스에 의존하는 Theater도 함께 변경해야 함
    • 다른 클래스가 Audience의 내부에 많이 알수록 Audience 변경이 어려워짐

    ⇒ 객체 사이의 의존성 과 관련된 문제

  • 의존성

    • 변경에 대한 영향을 암시
    • 어떤 객체 변경 시 그 객체에 의존하는 다른 객체도 함께 변경 가능함
  • 객체 사이의 의존성을 완전히 없애는 것이 정답은 아님

    • 객체지향 설계 는 서로 의존하며 협력하는 객체들의 공동체를 구축하는 것
    • 우리의 목표는 앱의 기능 구현에 필요한 최소한의 의존성만 유지하고 불필요한 의존성을 제거하는 것
  • 결합도 가 높다

    • 객체 사이의 의존성이 과한 경우
    • 두 객체 사이의 결합도가 높을수록 함께 변경될 확률도 높아져 변경하기 어려워짐

      ⇒ 설계의 목표는 객체 사이의 결합도를 낮춰 변경이 용이한 설계를 만드는 것

03. 설계 개선하기

  • 이 코드는 기능은 제대로 수행하지만 이해하기 어렵고 변경이 어려움
  • 의도를 정확하게 의사소통하지 못해 코드가 이해하기 어려워짐
  • 해결 방법 : Theater가 Audience와 TicketSeller에 관해 너무 세세한 부분까지 알지 못하도록 정보를 차단

    ⇒ 관람객과 판매원을 자율적인 존재 로 만들면 됨

자율성을 높이자

  • 설계 변경이 어려운 이유

    • Theater가 Audience, TicketSeller, Audience 소유의 Bag과 TicketSeller가 근무하는 TicketOffice까지 마음대로 접근 가능하기 때문
  • 해결 방법 : Audience와 TicketSeller가 직접 Bag과 TicketOffice를 처리하는 자율적인 존재가 되도록 설계를 변경하는 것

    ⇒ ticketOffice에 대한 접근은 오직 TicketSeller에서만 존재하게 됨

  • 캡슐화

    • 개념적이나 물리적으로 객체 내부의 세부적인 사항을 감추는 것
    • 목적: 변경하기 쉬운 객체를 만드는 것
    • 캡슐화를 통해 객체 내부로의 접근을 제한하면 객체와 객체 사이의 결합도를 낮출 수 있어 설계를 좀 더 쉽게 변경 가능
  • 수정 후

    • Theater은 오직 TicketSeller의 인터페이스(interface) 에만 의존함
    • TicketSeller가 내부에 TicketOffice 인스턴스를 포함하고 있다는 사실은 구현(implementation) 영역에 속함

      ⇒ 객체를 인터페이스와 구현으로 나누고 인터페이스만을 공개하는 것은 객체 사이의 결합도를 낮추고 변경하기 쉬운 코드를 작성하기 위해 따라야 하는 가장 기본적인 설계 원칙

    • 캡슐화 개선 후 가장 큰 개선점은 Audience와 TicketSeller가 내부 구현을 외부에 노출하지 않고 자신의 문제를 스스로 책임지고 해결한다는 것

      ⇒ 자율적인 존재가 된 것

무엇이 개선됐는가

  • 수정된 Audience와 TicketSeller는 자신이 가지는 모든 소지품을 스스로 관리함

    ⇒ 우리의 예상과 정확히 일치함

    ⇒ 코드의 독자와의 의사소통 관점에서 확실히 개선됨

어떻게 한 것인가

  • 자기 자신의 문제를 스스로 해결하도록 코드를 변경한 것

    ⇒ 객체의 자율성을 높이는 방향으로 설계를 개선함

캡슐화와 응집도

  • 핵심은 객체 내부의 상태를 캡슐화하고 객체 간 오직 메세지를 통해서만 상호작용하도록 만드는 것
  • 응집도 가 높다

    • 밀접하게 연관된 작업만을 수행하고 연관성 없는 작업은 다른 객체에게 위임하는 객체
    • 자신의 데이터를 스스로 처리하는 자율적인 객체는 결합도는 낮추고 응집도는 높일 수 있음
  • 객체의 응집도를 높이려면 객체 스스로 자신의 데이터를 책임져야 함

    • 객체는 자신의 데이터를 스스로 처리하는 자율적인 존재여야 함
    • 외부의 간섭을 최대한 배제하고 메시지를 통해서만 협력하는 자율적인 객체들의 공동체를 만들어야 함

절차지향과 객체지향

절차적 프로그래밍

  • 프로세스와 데이터를 별도의 모듈에 위치시키는 방식

    • 프로세스(Process) : Theater의 enter 메서드
    • 데이터(Data) : Audience, TicketSeller, Bag, TicketOffice
  • 모든 처리가 하나의 클래스 안에 위치하고 나머지 클래스는 단지 데이터의 역할만 수행
  • 직관에 위배됨 : 관람객과 판매원이 수동적인 존재일 뿐

    • 예상을 벗어나 코드 독자와 원활한 의사소통 불가
  • 데이터의 변경으로 인한 영향을 지역적으로 고립시키기 어려움

    • 변경하기 어려운 코드를 양산함
  • 변경하기 쉬운 설계

    • 한 번에 하나의 클래스만 변경할 수 있는 설계
    • 절차적 프로그래밍 : 프로세스가 필요한 모든 데이터에 의존해야 하기 때문에 변경에 취약할 수 밖에 없음

객체지향 프로그래밍

  • 프로세스와 데이터가 동일 모듈 내부에 위치하도록 프로그래밍하는 방식
  • 의존성을 적절히 통제하며 하나의 변경으로 인한 여파가 여러 클래스로 전파되는 것을 억제함
  • 훌륭한 객체지향 설계의 핵심: 캡슐화를 이용해 의존성을 적절히 관리해 객체 사이의 결합도를 낮추는 것

책임의 이동

  • 두 방식 차이에 근본적인 차이를 만드는 것
절차적 프로그래밍 객체지향 프로그래밍
작업 흐름이 주로 Theater에 의해 제어됨 제어 흐름이 각 객체에 적절히 분산돼 있음
⇒ 책임이 Theater에 집중돼있음 ⇒ 하나의 기능 완성에 필요한 책임이 여러 객체에 분산됨
데이터와 프로세스가 별도의 객체에 위치 데이터와 프로세스가 동일한 객체에 위치
  • 객체 지향 설계의 핵심: 적절한 객체에 적잘한 책임을 할당하는 것
  • 설계를 어렵게 만드는 것은 의존성

    • 해결 방법: 불필요한 의존성 제거로 객체 사이의 결합도 를 낮추는 것

      결합도 를 낮추기 위해 캡슐화 사용

더 개선할 수 있다

  • 기존 public 메서드들은 이제 내부에서만 사용되어 가시성을 private으로 변경함
  • 이 작은 메서드들을 제거하지 않은 이유는 코드의 중복을 제거하고 표현력을 높이기 위해서다.
  • 개선 후

    • TicketOffice의 자율성은 높였지만 전체 설계의 관점에서는 결합도가 상승함
    • 토론 후: TicketOffice의 자율성 < Audience에 대한 결합도를 낮추는 것
  • 설계에 대한 사실

    1. 어떤 기능을 설계하는 방법은 한 가지 이상일 수 있음
    2. 동일한 기능을 한 가지 이상의 방법으로 설계할 수 있어 설계는 트레이드오프의 산물임

그래, 거짓말이다!

  • 무생물 역시 스스로 행동하고 자기 자신을 책임지는 자율적인 존재로 취급함
  • 의인화

    • 현실에서는 수동적이라도 객체지향에서는 능동적이고 자율적인 존재로 봄
    • 능동적이고 자율적인 존재로 소프트웨어 객체를 설계하는 원칙
  • 일상적인 체계에서는 어떤 사건이 일어나기 위해 반드시 인간 에이전트가 필요하지만 객체들은 그들 자신의 체계 안에서 능동적이고 자율적인 에이전트
  • 훌륭한 객체지향 설계란 소프트웨어를 구성하는 모든 객체들이 자율적으로 행동하는 설계

04. 객체지향 설계

설계가 왜 필요한가

  • 설계란 코드를 배치하는 것이다.
  • 설계를 구현과 떨어트려서 이야기하는 것은 불가능하다.
  • 설계는 코드를 작성하는 매 순간 코드를 어떻게 배치할 것인지를 결정하는 과정에서 나온다.
  • 좋은 설계

    • 오늘 완성해야 하는 기능을 구현하는 코드
    • 내일 쉽게 변경할 수 있는 코드
  • 설계가 중요한 이유는 요구사항이 항상 변경되기 때문
  • 요구사항 변경은 필연적으로 코드 수정을 초래하고, 코드 수정은 버그가 발생할 가능성을 높임

객체지향 설계

  • 의존성을 효율적으로 통제할 수 있는 다양한 방법을 제공해 요구사항 변경에 좀 더 수월하게 대응할 가능성을 높여줌
  • 코드 변경 측면에서는 객체지향이 과거의 다른 방법보다 안정감을 줌
  • 변경 가능 코드: 이해하기 쉬운 코드
  • 객체지향은 세상에 대해 예상하는 방식대로 객체가 행동하리라는 것을 보장해 코드를 좀 더 쉽게 이해할 수 있게 함
  • 훌륭한 객체지향 설계: 객체 사이의 의존성을 적절하게 관리하는 설계
  • 협력하는 객체들 사이의 의존성을 적절히 조절해 변경에 용이한 설계를 만드는 것

Written by@jaeeun
I explain with words and code. I explain with words and code. I explain with words and code.