Chapter 7

함께 모으기

  • 커피 전문점 도메인
  • 도메인 모델 만들기
  • 협력 설계하기
  • 코드로 옮기기
  • 코드와 세 가지 관점

1~6장에서 배운 모든 것을 하나의 예제로 함께 모으는 시간이야. 커피 전문점 도메인을 가지고, 도메인 모델 -> 협력 설계 -> 코드 구현까지 전 과정을 처음부터 끝까지 따라가보자. 이론이 실전에서 어떻게 돌아가는지를 보여주는 총정리거든.

예제의 배경은 간단한 커피 전문점이야. 1장에서 나왔던 그 카페를 다시 가져오지.

등장하는 개념들:

  • 손님(Customer) — 커피를 주문하는 사람
  • 캐셔(Cashier) — 주문을 받는 사람. 바리스타에게 제조를 요청
  • 바리스타(Barista) — 커피를 만드는 사람
  • 메뉴(Menu) — 판매 가능한 음료 목록
  • 메뉴 항목(MenuItem) — 개별 음료의 이름과 가격
  • 커피(Coffee) — 바리스타가 만들어낸 결과물

단순한 도메인이지만, 이 안에 객체지향의 핵심 원리가 전부 녹아 있어. 저자가 의도적으로 복잡하지 않은 예제를 택한 건, 원리에 집중하기 위해서야.

6장에서 배운 대로, 먼저 도메인 모델을 만들어보자. 핵심 개념과 관계를 파악하는 거지.

  • 손님은 메뉴를 봐 — 손님과 메뉴 사이에 관계가 있어
  • 메뉴는 메뉴 항목들을 포함해 — 메뉴와 메뉴 항목 사이에 포함 관계
  • 손님은 캐셔에게 주문해 — 손님과 캐셔 사이에 관계
  • 캐셔는 바리스타에게 제조를 요청해 — 캐셔와 바리스타 사이에 관계
  • 바리스타는 커피를 만들어 — 바리스타와 커피 사이에 관계

이 관계들이 도메인 모델의 뼈대야. 여기서 주의할 점 — 아직 코드를 생각하면 안 돼. 클래스를 설계하는 게 아니라, 비즈니스 도메인의 개념을 정리하는 거거든.

도메인 모델을 잡았으면, 이제 유스케이스를 실현하는 협력을 설계하지.

유스케이스는 "손님이 커피를 주문한다"는 단순한 시나리오야.

4장의 책임-주도 설계, 5장의 What/Who 사이클을 적용해보자:

1단계: 메시지를 결정해 (What)

시스템에 필요한 첫 번째 메시지: "커피를 주문하라." 이 메시지가 전체 협력의 시작점이야.

2단계: 메시지를 받을 객체를 결정해 (Who)

"커피를 주문하라"는 메시지를 누가 받을까? 손님이지. 손님이 주문하는 행위의 주체니까.

3단계: 손님이 책임을 수행하는 데 필요한 추가 협력을 찾아

손님은 주문하려면 메뉴를 알아야 해. 그래서 메뉴 객체에게 "메뉴 항목을 찾아라"는 메시지를 보내지.

메뉴는 이름에 해당하는 메뉴 항목을 찾아서 돌려줘.

4단계: 계속 이어가

손님은 메뉴 항목을 가지고 캐셔에게 "주문하라"는 메시지를 보내. 캐셔는 바리스타에게 "커피를 제조하라"는 메시지를 보내지. 바리스타는 커피를 만들어서 돌려줘.

전체 메시지 흐름:

  1. 손님 -> 메뉴: "아메리카노 메뉴 항목을 찾아라"
  2. 메뉴 -> 손님: 메뉴 항목 반환
  3. 손님 -> 캐셔: "이 메뉴 항목으로 주문한다"
  4. 캐셔 -> 바리스타: "이 메뉴 항목의 커피를 제조하라"
  5. 바리스타 -> 캐셔: 커피 반환
  6. 캐셔 -> 손님: 커피 전달

이 과정에서 5장의 원칙들이 적용되지:

  • 묻지 말고 시켜라 — 손님이 메뉴의 내부 구조를 뒤지지 않고, "찾아달라"고 메시지를 보내
  • 자율적인 책임 — 바리스타에게 "커피를 제조하라"고만 했지, "원두를 갈고 물을 끓이고..."라고 구체적으로 지시하지 않았잖아
  • 인터페이스와 구현의 분리 — 캐셔는 바리스타가 어떻게 커피를 만드는지 몰라. "제조하라"는 메시지만 보내고 결과만 받지

협력을 설계했으니, 이제 코드로 구현하자. 도메인 모델의 개념이 클래스가 되고, 협력에서 정의한 메시지가 메서드가 돼.

각 클래스의 책임:

  • Customer — 메뉴에서 항목을 찾고, 캐셔에게 주문하는 것
  • Menu — 이름으로 메뉴 항목을 찾아서 반환하는 것
  • MenuItem — 음료의 이름과 가격을 아는 것
  • Cashier — 주문을 받아서 바리스타에게 제조를 요청하는 것
  • Barista — 커피를 만드는 것
  • Coffee — 만들어진 커피. 이름과 가격 정보를 가짐

여기서 도메인 모델과 코드의 일치가 드러나지. 도메인에서 "바리스타가 커피를 만든다"고 했으면, 코드에서도 Barista 클래스에 makeCoffee() 메서드가 있어. 도메인 전문가(카페 사장)와 개발자가 같은 언어로 소통할 수 있는 구조야.

코드를 작성하면서 자연스럽게 적용되는 원칙들:

  • 캡슐화 — Customer는 Menu의 내부 구조(리스트인지 맵인지)를 몰라
  • 다형성 — 바리스타 역할을 다른 객체로 대체할 수 있지. 자동 커피 머신 객체로 바꿔도 Cashier의 코드는 안 바뀌거든
  • 높은 응집도 — 각 클래스가 자기 책임에 필요한 상태만 가지고 있어. Coffee의 가격은 MenuItem에서 오지, Barista가 알고 있는 게 아니야

저자는 마지막으로, 잘 작성된 코드를 세 가지 관점에서 바라볼 수 있어야 한다고 말해.

개념 관점 (Conceptual Perspective) — 도메인 모델의 관점에서 코드를 보는 거야. 클래스 이름과 관계가 도메인의 개념과 일치하는가? Customer, Barista, Menu, Coffee 같은 클래스 이름이 도메인 전문가에게도 의미가 통하는가? 클래스 간의 관계가 도메인의 관계를 반영하는가? 이 관점에서 코드가 깔끔하면, 도메인 지식이 있는 사람이 코드를 읽을 때 구조를 쉽게 이해할 수 있지.

명세 관점 (Specification Perspective) — 인터페이스의 관점에서 코드를 보는 거야. 각 클래스의 퍼블릭 인터페이스가 명확한가? 클래스가 외부에 공개하는 메서드가 "무엇을 하는가"를 명확히 표현하는가? 구현 세부사항이 인터페이스에 노출되지 않는가? 이 관점에서 코드가 깔끔하면, 다른 개발자가 클래스를 사용할 때 내부를 몰라도 인터페이스만 보고 쓸 수 있어.

구현 관점 (Implementation Perspective) — 내부 구현의 관점에서 코드를 보는 거야. 각 클래스의 내부 로직과 상태 관리가 적절한가? 메서드 내부의 알고리즘이 효율적인가? 상태가 적절히 캡슐화되어 있는가? 불필요한 의존성이 없는가? 이 관점에서 코드가 깔끔하면, 내부를 수정할 때 외부에 영향을 주지 않고 자유롭게 바꿀 수 있지.

세 관점을 동시에 만족시키는 코드가 좋은 코드야. 도메인과 일치하고(개념), 인터페이스가 명확하고(명세), 구현이 깔끔한(구현) 코드. 그리고 이 세 관점은 서로 겹치지 않게 분리되어야 해. 인터페이스를 바꾸지 않고 구현만 바꿀 수 있어야 하고, 도메인 모델이 바뀌지 않는 한 클래스 구조도 안정적이어야 하거든.


정리

7장 읽고 기억할 거 세 가지:

  1. 도메인 모델 -> 협력 설계 -> 코드 구현. 이 순서가 객체지향 설계의 전체 흐름이야. 클래스부터 시작하지 마
  2. What/Who 사이클로 협력을 설계해라. 필요한 메시지를 먼저 정하고, 그 메시지를 받을 객체를 나중에 정해. 이걸 반복하면 협력이 완성되지
  3. 코드를 세 가지 관점(개념, 명세, 구현)에서 바라봐라. 도메인과 일치하고, 인터페이스가 명확하고, 구현이 캡슐화된 코드가 좋은 객체지향 코드야