오브젝트 실전 강의 교재¶
3장 — 역할, 책임, 협력¶
원서: 조영호 『오브젝트: 코드로 이해하는 객체지향 설계』 대상: Java/Spring 백엔드 입문~중급 수강생 형식: 개념 → 비유 → 예시 → 핵심 교훈 → 현업 예제 → 함정 → 체크리스트 → 퀴즈(정답 분리)
0. 이 장을 시작하기 전에¶
0.1 학습 목표¶
- 객체지향의 본질이 클래스가 아니라 협력·책임·역할임을 이해한다.
- 책임 주도 설계(RDD)의 사고 순서(협력 → 책임 → 객체)를 익힌다.
- "메시지가 객체를 결정한다", "행동이 상태를 결정한다"라는 설계 격언을 체득한다.
0.2 큰 그림 — 설계의 사고 순서¶
[ 협력 ] 어떤 요청-응답으로 목표를 이룰까
│ (협력이 필요한 행동을 끌어낸다)
[ 책임 ] 그 행동(의무)을 누가 맡을까
│ (책임을 객체에게 할당한다)
[ 역할 ] 대체 가능한 책임 묶음 → 여러 객체가 채울 수 있는 '자리'
비유 — "연극 무대": 협력은 한 편의 공연(목표 달성을 위한 상호작용), 책임은 각 인물이 무대에서 해야 할 일(대사·동작), 역할은 그 인물의 배역입니다. 배역(역할)은 정해져 있고, 그 배역을 연기하는 배우(객체)는 공연마다 바뀔 수 있습니다.
0.3 현업에서 왜 중요한가¶
- 좋은 클래스를 먼저 떠올리는 게 아니라, 어떤 협력이 필요한가 → 어떤 책임이 필요한가 → 그 책임을 누구에게의 순서로 사고하면 자연스럽게 응집도 높은 설계가 나옵니다.
- 인터페이스 기반 설계(역할)는 그대로 다형성·교체 가능성·테스트 용이성으로 이어집니다.
1. 협력¶
비유 — "식당의 주문 흐름"¶
손님이 웨이터에게 "파스타 주세요"라고 요청하고, 웨이터는 주방에 전달하고, 주방은 요리해 응답합니다. 아무도 남의 일을 대신하지 않습니다. 협력은 이런 요청과 응답의 연쇄입니다.
핵심¶
- 협력: 객체들이 메시지(요청)를 주고받으며 함께 목표를 달성하는 것.
- 한 객체가 혼자 처리할 수 없는 일을, 다른 객체에게 요청함으로써 협력이 생긴다.
- 협력이 설계의 문맥을 결정한다: 객체의 행동(책임)은 "어떤 협력에 참여하는가"에 따라 정해진다. 협력 없이 객체의 책임을 정할 수 없다.
2장 복습:
Screening이 혼자 요금을 못 구하니Movie에게 요청하고,Movie는DiscountPolicy에게 요청했습니다. 이 협력의 사슬이 각 객체의 책임을 만들어냈습니다.
2. 책임¶
2.1 책임이란 무엇인가 — "하는 것"과 "아는 것"¶
책임은 객체가 협력에서 맡는 의무입니다. 두 범주로 나뉩니다.
- 하는 것(doing): 객체를 생성/계산하거나, 다른 객체의 행동을 시작/제어/조정한다.
- 아는 것(knowing): 사적 정보를 알고, 관련된 객체를 알고, 자신이 유도/계산할 수 있는 것을 안다.
책임은 곧 객체의 공개 인터페이스 후보입니다. "이 객체는 무엇을 할 줄 알고, 무엇을 아는가"가 메시지가 됩니다.
2.2 책임 주도 설계 (Responsibility-Driven Design)¶
사고 순서: 시스템이 해야 할 일(협력)을 → 더 작은 책임으로 나누고 → 그 책임을 수행하기에 적절한 객체에게 할당한다.
데이터(필드)부터 정하지 않고, 책임부터 정합니다. 데이터 중심 설계의 함정은 4장에서 다룹니다.
2.3 메시지가 객체를 결정한다 (순서를 뒤집어라)¶
비유 — "채용"¶
회사를 만들 때, 사람부터 뽑아놓고 "이제 무슨 일 시키지?"라고 하지 않습니다. 필요한 일(메시지)을 먼저 정의하고, 그 일을 가장 잘할 사람(객체)을 찾습니다.
// 나쁜 순서: 객체(클래스)를 먼저 만들고 → 거기 맞춰 메시지를 끼워넣음
// 좋은 순서: 협력에 필요한 '메시지'를 먼저 정하고 → 그 메시지를 처리할 객체를 찾음
// 예: "이 상영의 요금을 계산하라"는 메시지가 먼저 필요했다
// → 그 메시지를 가장 잘 처리할 객체로 Movie를 선택
movie.calculateMovieFee(screening); // 메시지가 먼저, Movie는 그 뒤
메시지를 먼저 정하면, 객체의 인터페이스가 클라이언트가 원하는 것으로 채워집니다(꼭 필요한 public 메서드만 생김 → 최소 인터페이스).
2.4 행동이 상태를 결정한다¶
비유 — "이력서를 거꾸로 쓰지 마라"¶
스펙(상태)부터 나열한 이력서가 아니라, "무슨 일을 할 수 있는가(행동)"부터 정의하고 그 일에 필요한 스펙(상태)을 채웁니다.
- 상태 먼저(나쁨): 필드를 먼저 정하면, 그 데이터를 노출하는 getter/setter가 생기고 캡슐화가 약해진다. 협력은 뒷전이 된다.
- 행동 먼저(좋음): "협력에서 이 객체가 무엇을 하는가(책임)"를 먼저 정하고, 그 행동에 꼭 필요한 상태만 추가한다.
// 행동 먼저: Account가 협력에서 '출금/입금'을 한다 → 그 행동에 필요한 balance만 둔다
class Account {
private long balance; // 행동(withdraw/deposit)에 필요해서 생긴 상태
void withdraw(long amount) { ... }
void deposit(long amount) { ... }
}
연결: 1장의 "자율적 객체", 클린코드 "묻지 말고 시켜라"와 한 줄기입니다.
3. 역할¶
3.1 역할이란 — "대체 가능한 책임의 집합"¶
같은 협력에서, 책임의 묶음을 여러 객체가 번갈아 수행할 수 있을 때, 그 자리를 역할(role)이라 부릅니다.
비유 — "배우와 배역"¶
연극의 "주인공"이라는 배역(역할)은 정해져 있고, 그것을 연기하는 배우(객체)는 공연마다 다릅니다. 한 배우가 여러 배역을, 한 배역을 여러 배우가 맡을 수 있습니다.
// '할인 정책'이라는 역할(배역) = DiscountPolicy
// 그 배역을 연기하는 배우(객체) = AmountDiscountPolicy / PercentDiscountPolicy / NoneDiscountPolicy
DiscountPolicy policy = new PercentDiscountPolicy(...); // 오늘 공연의 배우
3.2 역할 = 협력의 추상화¶
- 협력을 "구체 객체들의 상호작용"이 아니라 "역할들의 상호작용"으로 보면, 협력이 추상적이고 재사용 가능해집니다.
Movie는 "할인 정책이라는 역할"과 협력합니다. 어떤 구체 정책이 오든 협력 구조는 그대로입니다.- 역할은 곧 인터페이스/추상 클래스로 구현되고, 역할을 채우는 다양한 객체가 다형성을 만듭니다.
3.3 객체 대 역할¶
- 협력에 등장하는 자리가 여러 객체로 대체 가능하면 → 역할(추상 타입).
- 항상 한 종류 객체만 온다면 → 굳이 역할로 추상화하지 않고 구체 객체로 둬도 됩니다(과한 추상화 경계 — 9장).
핵심 교훈¶
- 객체지향의 본질은 클래스가 아니라 협력·책임·역할이다.
- 설계 순서는 협력 → 책임 → 객체(책임 주도 설계).
- 메시지가 객체를 결정한다: 필요한 메시지를 먼저 정하고, 처리할 객체를 찾아라.
- 행동이 상태를 결정한다: 책임(행동)을 먼저 정하고, 필요한 상태만 채워라.
- 역할은 대체 가능한 책임의 집합(배역) — 협력을 추상화하고 다형성을 낳는다.
현업 예제 — 인터페이스(역할)부터 설계하기¶
// 1) 협력에 필요한 '메시지'를 역할(인터페이스)로 먼저 정의
public interface PaymentGateway { // '결제 처리'라는 역할
PaymentResult pay(Order order, Money amount);
}
// 2) 그 역할을 여러 객체(배우)가 연기
class TossPaymentGateway implements PaymentGateway { ... }
class KakaoPaymentGateway implements PaymentGateway { ... }
class FakePaymentGateway implements PaymentGateway { ... } // 테스트용 배우
// 3) 클라이언트는 역할에만 의존(누가 연기하든 무관)
@Service
class CheckoutService {
private final PaymentGateway gateway; // 역할에 의존
CheckoutService(PaymentGateway gateway) { this.gateway = gateway; }
}
역할(인터페이스)을 먼저, 배우(구현)는 나중에 — 이것이 책임 주도 설계가 Spring DI·테스트 용이성으로 이어지는 통로입니다(이펙티브 자바 5·20, 2장의 전략 구조와 동일).
함정 / 주의¶
- 상태부터 설계하는 습관을 경계하라. ERD/필드부터 그리면 데이터 중심 설계로 빠져 캡슐화가 약해집니다(4장에서 그 폐해를 본격적으로 봅니다).
- 모든 자리를 역할로 추상화하지 마라. 대체 가능성이 없는데 인터페이스부터 만들면 추측성 일반화(리팩터링 3.15)입니다.
- 책임을 엉뚱한 객체에 두면 기능 편애(리팩터링 3.9)가 생깁니다. "정보를 가진 객체에게 책임을"(5장 정보 전문가).
체크리스트 (설계 사고 점검)¶
- 클래스가 아니라 협력(요청-응답)부터 떠올렸는가
- 필요한 메시지를 먼저 정하고 객체를 찾았는가
- 데이터(상태)가 아니라 행동(책임)을 먼저 정했는가
- 대체 가능한 자리를 역할(인터페이스/추상)로 표현했는가
- 책임이 그 일을 가장 잘 아는 객체에 있는가(엉뚱한 곳에 있지 않은가)
퀴즈¶
- "협력이 설계의 문맥을 결정한다"는 무슨 뜻인가?
- 책임의 두 범주는 무엇인가?
- "메시지가 객체를 결정한다"가 권하는 설계 순서는?
- "행동이 상태를 결정한다"를 어기면(상태 먼저) 어떤 문제가 생기나?
- 역할과 객체의 관계를 "배우와 배역"으로 설명하라.
정답·해설¶
- 객체의 책임(행동)은 혼자 정해지지 않고, 어떤 협력에 참여하느냐에 따라 결정된다는 뜻입니다. 협력이라는 문맥이 있어야 "이 객체가 무엇을 해야 하는지"가 정해집니다.
- 하는 것(doing) — 계산/생성/제어·조정, 아는 것(knowing) — 사적 정보·관련 객체·유도 가능한 값을 아는 것.
- 객체(클래스)를 먼저 만들지 말고, 협력에 필요한 메시지를 먼저 정의한 뒤 그 메시지를 가장 잘 처리할 객체를 찾는 순서. 그래야 인터페이스가 클라이언트가 원하는 최소한으로 채워집니다.
- 필드(상태)부터 정하면 그것을 노출하는 getter/setter가 생겨 캡슐화가 약해지고, 협력보다 데이터에 초점이 쏠려 데이터 중심 설계로 빠집니다.
- 역할 = 배역(정해진 자리/책임 묶음), 객체 = 배우(그 배역을 연기). 한 배역을 여러 배우가 맡을 수 있어(다형성), 협력을 추상적·재사용 가능하게 만듭니다.
다음 장 예고 — 4장: 설계 품질과 트레이드오프¶
일부러 데이터 중심으로 영화 예매 시스템을 다시 설계해, 그것이 왜 캡슐화 위반·높은 결합도·낮은 응집도로 이어지는지 해부합니다. 3장에서 배운 "책임/행동 우선"과 대비되는 반면교사 장입니다.