객체지향의 사실과 오해 6장

6장 객체 지도

길을 직접 알려주는 방법 vs 지도를 이용하는 방법

  • 길을 직접 알려주는 방법
    • 기능적이고 해결 방법 지향적인 접근법
    • 길을 찾는데 필요한 구체적인 기능을 제공한다.
    • 현재의 요구만을 만족시킬 수 있다.
  • 지도를 이용하는 방법
    • 구조적이고 문제 지향적인 접근법
    • 길을 찾는데 필요한 구조를 제공한다.
    • 다양한 목적을 위해 재사용될 수 있다.(범용적)
    • 지도를 제작한 사람들이 지도를 만들 때는 지도를 사용할 사람이 구체적으로 어떤 목적으로 사용할지 알지 못한다.
    • 기능에 대한 요구사항이 계속 변함에도 지도는 모든 요구사항을 수용할 수 있다.
      • 기능에 비해 상대적으로 잘 변하지 않는 안정적인 지형 정보를 기반으로 하고 있기 때문이다.

:bulb: 기능이 아니라 구조를 기반으로 모델을 구축하는 편이 좀 더 범용적이고 이해하기 쉬우며 변경에 안정적이다. 객체지향 개발 방법은 안정적인 구조에 변경이 빈번하게 발생하는 기능을 종속시키는 방법과 유사하다.

기능 설계 대 구조 설계

  1. 기능 측면의 설계
    • 제품이 사용자를 위해 무엇을 할 수 있는지에 초점을 맞춘다.
    • 시스템 기능은 더 작은 기능으로 분해되고 각 기능은 서로 밀접하게 관련된 하나의 덩어리로 이루어진다.
    • 변경될 경우 기능의 축을 따라 설계된 소프트웨어가 전체적으로 요동치게 된다.
    • ex) 기능 분해 방법
  2. 구조 측면의 설계
    • 제품의 형태가 어떠해야 하는지에 초점을 맞춘다.
    • 시스템 기능을 객체 간의 책임으로 분배한다.
    • 시스템 기능은 더 작은 책임으로 분할되고 적절한 객체에게 분배되기 때문에 기능이 변경되더라도 객체 간의 구조는 그대로 유지된다.

훌륭한 기능이 훌륭한 소프트웨어를 만드는 충분조건이라고 한다면, 훌륭한 구조는 훌륭한 소프트웨어를 만들기 위한 필요조건이다. 성공적인 소프트웨어들이 지닌 공통적인 특징은 훌륭한 기능을 제공하는 동시에 사용자가 원하는 새로운 기능을 빠르고 안정적으로 추가할 수 있다는 것이다.

미래에 대비하는 가장 좋은 방법은 변경을 예측하는 것이 아니라 변경을 수용할 수 있는 선택의 여지를 설계에 마련해 놓는 것이다.

두 가지 재료: 기능과 구조

  • 구조는 사용자나 이해관계자들이 도메인(domain)에 관해 생각하는 개념과 개념들 간의 관계로 표현한다.
  • 기능은 사용자의 목표를 만족시키기 위해 책임을 수행하는 시스템의 행위로 표현한다.

안정적인 재료: 구조

도메인 모델

도메인

소프트웨어를 사용하는 사람들은 자신이 관심을 가지고 있는 특정한 분야의 문제를 해결하기 위해 소프트웨어를 사용한다. 이처럼 사용자가 프로그램을 사용하는 대상 분야를 도메인이라고 한다.

모델

지식을 선택적으로 단순화하고 의식적으로 구조화한 형태다. 복잡성을 관리하기 위해 사용하는 기본적인 도구다.

도메인 모델이란 사용자가 프로그램을 사용하는 대상 영역에 관한 지식을 선택적으로 단순화하고 의식적으로 구조화한 형태다. 소프트웨어 개발과 관련된 이해관계자들이 도메인에 대해 생각하는 관점이다. 다른 말로는 이해관계자들이 바라보는 멘탈 모델(Mental Model)이라고 한다. 여기서 멘탈 모델이란 사람들이 자기 자신, 다른 사람, 환경, 자신이 상호작용하는 사물에 대해 갖는 모형이다.

도널드 노먼(Donald Norman)은 제품을 설계할 때 제품에 관한 모든 것이 사용자들이 제품에 대해 가지고 있는 멘탈 모델과 정확하게 일치해야 한다고 주장한다.

  • 사용자 모델: 사용자가 제품에 대해 가지고 있는 개념들의 모습
  • 디자인 모델: 설계자가 마음 속에 가지고 있는 시스템에 대한 개념화
  • 시스템 이미지: 최종 제품

설계자는 디자인 모델을 기반으로 만든 시스템 이미지가 사용자 모델을 정확하게 반영하도록 노력해야 한다.

도메인 모델은 도메인에 대한 사용자 모델, 디자인 모델, 시스템 이미지를 포괄하도록 추상화한 소프트웨어 모델이다.

도메인의 모습을 담을 수 있는 객체지향

도메인 모델은 사용자들이 도메인을 바라보는 관점이며, 설계자가 시스템의 구조를 바라보는 관점인 동시에 소프트웨어 안에 구현된 코드의 모습 그 자체이다.

객체지향은 이런 요구사항을 가장 범용적으로 만족시킬 수 있는 거의 유일한 모델링 패러다임이다. 사용자들이 이해하고 있는 도메인의 구조와 최대한 유사하게 코드를 구조화할 수 있다. 동적인 객체가 가진 복잡성을 극복하기 위해 정적인 타입을 이용해 단순화할 수 있으며 클래스라는 도구를 이용해 타입을 코드 안으로 옮길 수 있다. 결과적으로 객체지향을 이용하면 도메인에 대한 사용자 모델, 디자인 모델, 시스템 이미지 모두가 유사한 모습을 유지하도록 만드는 것이 가능하다. 이런 특징을 연결완전성 또는 표현의 차이라고 한다.

표현적 차이

소프트웨어 객체는 현실 객체를 모방한 것이 아니라 은유를 기반으로 재창조한 것이다. 소프트웨어 객체는 현실 객체가 갖지 못한 특성을 가질 수도 있고 현실 객체가 하지 못하는 행동을 할 수도 있다. 이처럼 소프트웨어 객체와 현실 객체 사이의 의미적 거리를 가리켜 표현적 차이 또는 의미적 차이라고 한다.

대부분의 소프트웨어의 도메인은 현실에 존재하지 않는 가상의 세계를 대상으로 한다. 그래서 소프트웨어 객체를 창조하기 위해 우리가 은유해야 하는 대상은 도메인 모델이다. 도메인 모델을 기반으로 설계하고 구현하는 것은 사용자가 도메인을 바라보는 관점을 그대로 코드에 반영할 수 있게 한다. 결과적으로 표현적 차이는 줄어들 것이며, 사용자의 멘탈 모델이 그대로 코드에 녹아 스며들게 될 것이다.

코드의 구조가 도메인의 구조를 잘 반영하는 경우, 도메인을 이해하면 코드를 이해하기가 훨씬 수월해진다. 이는 표현적 차이가 중요한 이유다.

불안정한 기능을 담는 안정적인 도메인 모델

도메인에 대한 사용자의 관점을 반영해야 하는 이유는 사용자들이 누구보다도 도메인의 ‘본질적인’ 측면을 가장 잘 이해하고 있기 때문이다. 여기서 본질적이라는 것은 변경이 적고 비교적 그 특성이 오랜 시간 유지된다는 것을 의미한다. 즉, 사용자 모델에 포함된 개념과 규칙은 변경될 확률이 적기 때문에 사용자 모델을 기반으로 설계와 코드를 만들면 변경에 쉽게 대처할 수 있는 가능성이 커진다. 이는 도메인 모델이 기능을 담을 수 있는 안정적인 구조를 제공할 수 있음을 의미한다.

안정적인 구조를 제공하는 도메인 모델을 기반으로 소프트웨어의 구조를 설계하면 변경에 유연하게 대응할 수 있는 탄력적인 소프트웨어를 만들 수 있다.

불안정한 재료: 기능

유스케이스

훌륭한 기능적 요구사항을 얻기 위해서는 목표를 가진 사용자와 사용자의 목표를 만족시키기 위해 일련의 절차를 수행하는 시스템 간의 ‘상호작용’ 관점에서 시스템을 바라봐야 한다.

사용자는 자신의 목표를 달성하기 위해 시스템에게 작업을 요청한다. 요청을 받은 시스템은 요청을 처리한 후 사용자에게 원하는 결과를 제공한다. 사용자는 시스템의 응답을 기반으로 또 다른 작업을 요청하고, 시스템은 요청을 다시 처리한 후 사용자에게 응답한다. 사용자와 시스템 사이의 상호작용은 사용자의 목표를 만족시키거나 에러 등의 이유로 상호작용을 더 이상 진행할 수 없을 때까지 계속된다. 이처럼 사용자의 목표를 달성하기 위해 사용자와 시스템 간에 이뤄지는 상호작용의 흐름을 텍스트로 정리한 것을 유스케이스라고 한다.

유스케이스는 사용자들의 목표를 중심으로 시스템의 기능적인 요구사항들을 이야기 형식으로 묶을 수 있다. 이는 각 기능이 유기적인 관계를 지닌 체계를 이룰 수 있게 한다. 또한, 요구사항을 기억하고 관리하는데 도움을 준다.

예시) 정기예금 이자 계산 유스케이스

  • 유스케이스명: 중도 해지 이자액을 계산한다.
  • 일차 액터: 예금주
  • 주요 성능 시나리오
    1. 예금주가 정기예금 계좌를 선택한다.
    2. 시스템은 정기예금 계좌 정보를 보여준다.
    3. 예금주가 금일 기준으로 예금을 해지할 경우 지급받을 수 있는 이자 계산을 요청한다.
    4. 시스템은 중도 해지 시 지급받을 수 있는 이자를 계산한 후 결과를 사용자에게 제공한다.
  • 확장
    • 3a. 사용자는 해지일자를 다른 일자로 입력할 수 있다.

일차 액터(primary actor)는 시스템의 서비스 중 하나를 요청하는 이해관계자로, 하나의 목표를 가지고 유스케이스를 시작하는 액터를 의미한다.

유스케이스의 특성

  1. 유스케이스는 사용자와 시스템 간의 상호작용을 보여주는 ‘텍스트’다.
    • 사용자와 시스템 간의 상호작용을 일련의 이야기 흐름으로 표현한다.
  2. 유스케이스는 하나의 시나리오가 아니라 여러 시나리오들의 집합이다.
    • 시나리오는 유스케이스를 통해 시스템을 사용하는 하나의 특정한 이야기 또는 경로다.
    • 시나리오를 유스케이스 인스턴스(use case instance)라고도 한다.
    • 위에서 설명하느 정기예금 이자 계산 유스케이스도 2가지의 시나리오를 포함한다.
      • [1] 예금주가 계좌를 선택하고 당일까지의 이자액을 계산하는 것
      • [2] 예금주가 계좌를 선택하고 특정 일자까지의 이자액을 계산하는 것
      • 이는 이자액 계산이라는 사용자의 목표와 관련된 모든 시나리오의 집합이다.
  3. 유스케이스는 단순한 피처(feature) 목록과 다르다.
    • 피처: 시스템이 수행해야 하는 기능의 목록을 단순하게 나열한 것
    • 유스케이스: 이야기를 통해 연관된 기능들을 함께 묶을 수 있다.
      • 독립적인 피처들을 유스케이스로 묶고 사용자의 상호작용 흐름 속에서 피처들을 포함하는 이야기를 제공함으로써 시스템의 기능에 대해 의사소통할 수 있는 문맥을 얻을 수 있다.
  4. 유스케이스는 사용자 인터페이스와 관련된 세부 정보를 포함하지 말아야 한다.
    • 자주 변경되는 사용자 인터페이스 요소는 배제한다.
    • 사용자 관점에서 시스템의 행위에 초점을 맞춘다.
    • 사용자 인터페이스를 배제한 유스케이스 형식을 본질적인 유스케이스(essential use case)라고 한다.
  5. 유스케이스는 내부 설계와 관련된 정보를 포함하지 않는다.
    • 유스케이스에서 객체 설계로의 전환은 경험과 상식과 의사소통을 기반으로 한 창조 작업이다.

유스케이스는 설계 기법도, 객체지향 기법도 아니다

유스케이스에는 시스템의 내부 구조나 실행 메커니즘에 관한 어떤 정보도 제공하지 않는다. 단지 사용자가 시스템을 통해 무엇을 얻을 수 있고 어떻게 상호작용할 수 있느냐에 관한 정보만 기술된다. 기능적 요구사항을 사용자의 목표라는 문맥을 중심으로 묶기 위한 정리 기법일 뿐이다. 유스케이스 안에는 영감을 불러일으킬 수 있는 약간의 힌트만이 들어있을 뿐이다.

재료 합치기: 기능과 구조의 통합

도메인 모델, 유스케이스, 그리고 책임-주도 설계

도메인 모델은 안정적인 구조를 개념화하기 위해, 유스케이스는 불안정한 기능을 서술하기 위해 가장 일반적으로 사용되는 도구다. 변경에 유연한 소프트웨어를 만들기 위해서는 유스케이스에 정리된 시스템의 기능을 도메인 모델을 기반으로 한 객체들의 책임으로 분배해야 한다.

시스템은 사용자와 만나는 경계에서 사용자의 목표를 만족시키기 위해 사용자와의 협력에 참여하는 커다란 객체다. 사용자에게 시스템이 수행하기로 약속한 기능은 결국 시스템의 책임으로 볼 수 있다. 또한, 시스템이 수행해야 하는 커다란 규모의 책임은 시스템 안에 살아가는 더 작은 크기의 객체들의 협력을 통해 구현될 수 있다.

책임-주도 설계

  1. 협력의 출발을 장식하는 첫 번째 메시지는 시스템의 기능을 시스템의 책임으로 바꾼 후 얻어진 것이다.
  2. 시스템에 할당된 커다란 책임은 시스템 안의 작은 규모의 객체들이 수행해야 하는 더 작은 규모의 책임으로 세분화된다.
  3. 도메인 모델에 포함된 개념을 은유하는 소프트웨어 객체를 선택해야 한다.
    • 소프트웨어와 코드 사이의 표현적 차이를 줄이는 것이다.
  4. 협력을 완성하는데 필요한 메시지를 식별하면서 객체들에게 책임을 할당한다.
  5. 협력에 참여하는 객체를 구현하기 위해 클래스를 추가하고 속성과 메서드를 구현한다.

책임-주도 설계는 유스케이스로부터 첫 번째 메시지와 사용자가 달성하려는 목표를, 도메인 모델로부터 기능을 수용할 수 있는 안정적인 구조를 제공받아 객체들의 협력 공동체를 창조한다.

[예시] 이자 계산 기능 구현

정기 예금을 중도 해지할 경우 지급받을 수 잇는 이자액을 계사하는 기능을 구현한다.

  1. 시스템이 ‘중도 해지 이자액을 계산하라’라는 메시지를 받는 거대한 객체가 된다.
  2. 도메인 모델을 통해, 이자 계산이라는 시스템 책임을 분배할 객체 구조를 구성한다.
  3. 책임을 분할하고 객체들에게 할당하면서 협력하는 객체들의 공동체를 형성한다.

    • 정기예금
      • 이자 계산을 시작하는 책임을 맡는다.
      • 해지 일자를 받아 약정 기간에 포함되는지 확인한 후 포함될 경우 계좌에게 이자 계산을 요청한다.
    • 계좌
      • 예금액과 해지 일자를 이자율에게 전달해 이자를 계산하게 한다.
    • 이자율
      • 전달받은 예금액과 해지 일자를 이용해 이자액을 계산한 후 이자액을 포함하는 이자를 생성해 반환한다.

이자 계산을 위한 클래스 다이어그램

객체의 이름은 도메인 모델에 포함된 개념으로부터 차용하고, 책임은 도메인 모델에 정의한 개념의 정의에 부합하도록 할당한다. 책임 할당의 기본 원칙은 책임을 수행하는 데 필요한 정보를 가진 객체엑 그 책임을 할당하는 것이다. 이는 관련된 상태와 행동을 함께 캡슐화하는 자율적인 객체를 낳는다.

유스케이스에서 출발해 객체들의 협력으로 이어지는 일련의 흐름은 객체 안에 다른 객체를 포함하는 재귀적 합성이라는 객체지향의 기본 개념을 잘 보여준다. 객체에 대한 재귀는 객체지향의 개념을 모든 추상화 수준에서 적용 가능하게 하는 동시에 객체지향 패러다임을 어떤 곳에서든 일관성 있게 적용할 수 있게 한다.

기능 변경을 흡수하는 안정적인 구조

도메인 모델이 구성하는 요소의 특징

  1. 도메인 모델을 구성하는 개념은 비즈니스가 없어지거나 완전히 개편되지 않는 한 안정적으로 유지된다.
    • 정기예금 도메인에서 정기예금, 계좌, 이자율, 이자는 정기예금이라는 금융상품이 없어지거나 완전히 개편되지 않는 한 안정적으로 유지된다.
  2. 도메인 모델을 구성하는 개념 간의 관계는 비즈니스 규칙을 기반으로 하기 때문에 비즈니스 정책이 크게 변경되지 않는 한 안정적으로 유지된다.
    • 계좌와 이자간의 0..1관계는 핵심적인 비즈니스 규칙(이자는 정기예금이 만기되거나 중도 해지하는 경우에 한해서 단 한번만 지급된다)이 변경되지 않는 한 동일하게 유지된다.

비즈니스 정책이나 규칙이 크게 변경되지 않는 한 시스템의 기능이 변경되더라도 객체 간의 관계는 일정하게 유지된다. 기능적인 요구사항이 변경될 경우 책임과 객체 간의 대응 관계만 수정될 뿐이다. 이는 변경에 대한 파급효과를 최소화하고 요구사항 변경에 유연하게 대응할 수 있는 시스템을 구축할 수 있게 한다.

연결완전성, 가역성

객체지향의 가장 큰 장점은 도메인을 모델링하기 위한 기법과 도메인을 프로그래밍하기 위해 사용하는 기법이 동일하다는 점이다. 이는 도메인 모델링에서 사용한 객체와 개념을 프로그래밍 설계에서의 객체와 클래스로 매끄럽게 변환할 수 있다. 이런 특성을 연결완전성라고 한다.

연결완전성의 역방향 역시 성립한다. 즉, 코드의 변경으로부터 도메인 모델의 변경 사항을 유추할 수 있다. 여기서 코드에서 모델로의 매끄러움 흐름을 의미하는 것을 가역성(reversibility)라고 한다.