앞 두 장이 코드였다면 이 장은 개념이다. 조영호는 여기서 잠시 코드를 내려놓고 묻는다 — 좋은 객체를 만들려면 무엇부터 생각해야 하는가. 초보의 답은 “어떤 클래스가 필요한가”이지만, 이 책의 답은 다르다. 클래스는 마지막에 온다. 먼저 오는 것은 협력이고, 협력이 책임을 부르고, 책임이 모여 역할이 되고, 역할을 수행할 존재로서 비로소 객체(클래스)가 정해진다. 이 순서를 뒤집지 않는 것이 책임 주도 설계다.
협력이 출발점이다
객체는 혼자 존재하지 않는다. 영화를 예매하려면 Screening이 Movie에게 요금을 묻고, Movie가 DiscountPolicy에게 할인을 묻는다. 하나의 목표(예매)를 이루기 위해 객체들이 요청과 응답을 주고받는 이 상호작용이 협력이다.
협력이 먼저인 이유는, 객체가 무엇을 할지가 그 객체 혼자서 정해지지 않기 때문이다. Movie가 “할인 금액을 계산할 줄 안다”는 능력은 예매라는 협력이 그것을 필요로 하기 때문에 생긴다. 협력이 요청하지 않는 능력은 군더더기다. 그래서 설계는 “이 객체는 무엇을 할 수 있어야 하지?”가 아니라 “이 목표를 이루려면 누가 무엇을 해줘야 하지?”에서 시작한다.
판단 기준: 어떤 메서드가 필요한지 막막하면, 그 메서드가 속한 협력 시나리오를 먼저 그린다. 요청하는 쪽이 있어야 응답할 책임이 정당화된다. 함정: 협력 없이 “이 클래스엔 이런 기능이 있으면 좋겠다”며 메서드를 채우면, 아무도 호출하지 않는 죽은 능력이 쌓인다.
책임이 협력을 구체화한다
협력 속에서 한 객체가 맡는 몫이 책임이다. 책임은 두 종류다 — 무언가를 아는 것(knowing)과 무언가를 하는 것(doing). Movie는 자기 기본 요금을 알고, 요금을 계산할 줄 안다. 책임은 “이 객체가 협력을 위해 제공해야 하는 것”의 목록이며, 이것이 나중에 메서드(와 필드)로 구현된다.
여기서 중요한 원칙 하나. 책임은 그 책임에 필요한 정보를 가진 객체에게 할당한다 — 정보 전문가(information expert)에게. 할인 금액 계산에 필요한 데이터를 가진 것은 DiscountPolicy이므로, 계산 책임도 거기 있어야 한다. 1장에서 Theater가 Audience의 가방을 뒤지던 코드가 나빴던 이유가 정확히 이것이다 — 가방의 정보는 Audience가 가졌는데 책임은 Theater가 가로챘다.
판단 기준: “이 일에 필요한 데이터를 누가 가졌는가”를 물으면 책임의 주인이 대개 드러난다. 함정: 책임을 데이터에서 떼어 놓으면(데이터는 A가, 그 데이터를 다루는 로직은 B가) B가 A의 내부를 캐물어야 하고, 결합이 깊어진다. 4장이 바로 이 실수를 일부러 저질러 보이는 장이다.
역할은 책임의 묶음이자 교체 가능한 슬롯
여러 책임이 하나의 목적으로 묶이면 역할이 된다. 역할은 “누가 오든 이 책임들만 수행하면 되는” 자리, 즉 슬롯이다. 예매 협력에서 “할인 정책”이라는 역할은 “할인 금액을 계산한다”는 책임을 요구하고, 이 슬롯에는 금액 할인이든 비율 할인이든 그 책임을 수행할 수 있는 누구나 들어갈 수 있다.
역할이 슬롯이라는 관점은 곧 다형성이다. 2장의 DiscountPolicy는 역할이었고, AmountDiscountPolicy/PercentDiscountPolicy는 그 슬롯에 꽂힌 객체였다. 협력을 역할들의 상호작용으로 설계하면, 구체 객체를 갈아 끼워도 협력 구조 자체는 그대로다.
flowchart LR C["협력<br/>(목표를 위한 상호작용)"] -->|필요한 일을 나눈다| R["책임<br/>(아는 것 / 하는 것)"] R -->|관련된 책임을 묶는다| Role["역할<br/>(교체 가능한 슬롯)"] Role -->|슬롯을 채운다| O["객체 / 클래스"] O -.->|이 협력에 참여하며<br/>다시 협력을 만든다| C
화살표의 방향이 이 장의 전부다. 설계는 왼쪽(협력)에서 오른쪽(객체)으로 흐른다. 클래스에서 시작하지 않는다.
판단 기준: 같은 책임을 여러 구현이 나눠 맡을 수 있으면 그 자리는 역할(추상)이고, 오직 하나뿐이면 그냥 객체다. 미리 역할로 추상화할지, 객체로 두었다가 나중에 뽑아낼지는 “정말 여러 후보가 있는가”로 판단한다. 함정: 후보가 하나뿐인데 미리 역할로 추상화하면, 쓰이지 않을 유연성을 위해 이해 비용만 치른다 — 역할은 필요가 증명된 뒤 추출해도 늦지 않다.
책임을 객체에게 묻는다 — 의인화
책임 주도 설계를 몸에 붙이는 실전 기법 하나가 의인화다. 객체를 데이터 구조가 아니라 인격을 가진 존재로 대하고 말을 건네 본다 — “너는 상영에 대해 할인 조건을 만족하는지 판단할 수 있니?” 이 질문에 DiscountCondition이 “그래, 내가 요일과 시간을 아니까 내가 답할게”라고 대답하면 책임의 주인이 정해진다. 반대로 “나는 데이터만 있고 판단은 남이 해”라고 대답하는 객체가 있다면, 그건 3장이 경계하는 수동적 데이터 덩어리다.
이 태도가 실용적인 이유는, 협력을 사람들의 대화로 상상하면 어색한 지점이 곧바로 드러나기 때문이다. Theater가 Audience의 지갑을 직접 뒤지는 장면(1장)은 사람 사이라면 무례하고 부자연스럽다 — 그 어색함이 곧 설계 결함의 신호다. 잘 설계된 협력은 정중한 요청과 자율적인 응답으로 이루어진 대화처럼 읽힌다.
판단 기준: 협력을 사람들의 대화로 옮겼을 때 어색하거나 무례한 요청(“네 속을 좀 보자”)이 있으면, 책임이 잘못 놓였다는 신호다. 함정: 의인화를 은유로만 여기고 넘기면 실전에서 쓰지 못한다 — 실제로 객체에게 소리 내어 질문을 던져 보는 것이 요령이다.
왜 이 순서인가
클래스부터 떠올리면 “이 클래스에 어떤 기능을 담을까”라는 데이터 중심 사고로 미끄러지기 쉽다. 반대로 협력에서 시작하면 책임이 자연스럽게 정보 전문가에게 배정되고, 객체는 자율적이 되며, 협력은 역할 단위라 유연해진다. 조영호가 순서를 강조하는 이유가 이것이다 — 같은 재료로도 사고 순서가 설계 품질을 가른다.
- 협력 → 책임 → 역할 → 객체. 이 방향을 지키면 자율적이고 유연한 설계가, 뒤집으면 데이터 중심의 경직된 설계가 나온다.
- 책임은 그 책임을 수행할 정보를 가진 객체에게 준다(정보 전문가). 데이터와 로직을 떼어 놓지 않는다.
- 역할은 교체 가능한 슬롯이고, 슬롯으로 협력을 설계하면 구체 객체를 갈아 끼워도 구조가 견딘다 — 다형성의 개념적 뿌리다.
다음장으로 4장