오브젝트 Ch.3~4 — 역할, 책임, 협력 / 설계 품질과 트레이드오프
협력이 책임을, 메시지가 객체를 결정한다는 책임 주도 설계의 관점과 데이터 중심 설계의 문제점을 정리한다.
시리즈 · BackEnd 서적11 / 17
- SQL 레벨업 Ch.1 — DBMS 아키텍처
- SQL 레벨업 Ch.3 — SQL의 조건 분기
- SQL 레벨업 Ch.4~5 — 집약과 자르기, 반복문
- SQL 레벨업 Ch.6 — 결합
- SQL 레벨업 Ch.7 — 서브쿼리
- SQL 레벨업 Ch.8 — SQL의 순서
- SQL 레벨업 Ch.9 — 갱신과 데이터 모델
- SQL 레벨업 Ch.10 — 인덱스 사용
- 오브젝트 Ch.1 — 객체, 설계
- 오브젝트 Ch.2 — 객체지향 프로그래밍
- 오브젝트 Ch.3~4 — 역할, 책임, 협력 / 설계 품질과 트레이드오프
- 오브젝트 Ch.5 — 책임 할당하기
- 오브젝트 Ch.6 — 메시지와 인터페이스
- 오브젝트 Ch.8 — 의존성 관리하기
- 오브젝트 Ch.9 — 유연한 설계
- 오브젝트 Ch.10, 12 — 상속과 코드 재사용, 다형성
- 오브젝트 Ch.13 — 서브클래싱과 서브타이핑
3장은 객체지향의 핵심을 역할(role), 책임(responsibility), 협력(collaboration)으로 설명하고, 4장은 그 반대편에 있는 데이터 중심 설계가 왜 변경에 취약한지를 다룬다. 두 장을 함께 정리한다.
협력
객체지향 시스템은 자율적인 객체들의 공동체이고, 협력은 그 세계에서 기능을 구현할 수 있는 유일한 방법이다.
자율적이라는 것은 외부의 객체가 오직 메시지만 전송할 수 있고, 메시지를 어떻게 처리할지는 수신한 객체가 스스로 결정한다는 뜻이다. 자신이 할 수 없는 일을 다른 객체에게 위임하면 협력에 참여하는 객체 전체의 자율성이 높아진다. 이를 위한 가장 기본적인 방법이 내부 구현의 캡슐화다.
협력이 설계를 위한 문맥을 결정한다
애플리케이션에 어떤 객체가 필요한 이유는 하나뿐이다. 그 객체가 어떤 협력에 참여하고 있기 때문이다. 객체가 협력에 참여할 수 있는 것은 협력에 필요한 행동을 갖고 있기 때문이고, 그 행동을 결정하는 것 역시 협력이다.
협력이 객체의 행동과 상태를 모두 결정하므로, 협력은 객체를 설계하는 데 필요한 문맥을 제공한다.
책임
협력에 참여하기 위해 객체가 수행하는 행동을 책임이라 한다. 책임은 ’하는 것(doing)’과 ’아는 것(knowing)’으로 나뉜다.
- 하는 것
- 객체를 생성하거나 계산을 수행하는 등 스스로 하는 것
- 다른 객체의 행동을 시작시키는 것
- 다른 객체의 활동을 제어하고 조절하는 것
- 아는 것
- 사적인 정보에 관해 아는 것
- 관련된 객체에 관해 아는 것
- 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것
어떤 책임을 수행하려면 그에 필요한 정보를 알 책임도 함께 진다. 객체지향 설계에서 가장 중요한 것은 책임이며, 적절한 책임을 적절한 객체에게 할당해야 단순하고 유연한 설계를 얻을 수 있다.
책임 주도 설계
- 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악한다.
- 시스템 책임을 더 작은 책임으로 분할한다.
- 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당한다.
- 객체가 책임을 수행하는 도중 다른 객체의 도움이 필요하면 이를 책임질 적절한 객체 또는 역할을 찾는다.
- 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 된다.
메시지가 객체를 결정한다
중요한 것은 메시지를 먼저 식별하고 그 메시지를 처리할 객체를 나중에 선택한다는 점이다. 객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택한다.
- “영화를 예매하라” → 적절한 객체인 Screening 선택
- “가격을 계산하라” → 적절한 객체인 Movie 선택
이렇게 하면 객체의 인터페이스는 충분히 추상적이면서 최소한의 크기를 유지할 수 있다.
역할
객체가 특정한 협력 안에서 수행하는 책임의 집합을 역할이라고 부른다.
비율 할인 정책과 금액 할인 정책의 책임은 똑같이 할인 금액을 계산하는 것이다. 둘을 위한 협력을 따로 구현하면 코드가 중복된다. 이때 ’할인 요금을 계산하라’라는 메시지에 응답할 수 있는 대표자를 두면 두 협력을 하나로 통합할 수 있다. 이 대표자는 두 종류의 객체를 교대로 바꿔 끼울 수 있는 슬롯이고, 이 슬롯이 바로 역할이다.
역할을 구현하는 일반적인 방법은 추상 클래스와 인터페이스다.
- 추상 클래스: 책임의 일부를 구현해 둔다.
- 인터페이스: 일체의 구현 없이 책임의 집합만 나열한다.
역할의 가장 큰 장점은 설계의 구성 요소를 추상화할 수 있다는 것이다. 세부사항에 억눌리지 않고 객체들 사이의 핵심적인 관계라는 큰 그림을 파악할 수 있다.
설계 품질과 트레이드오프 (Ch.4)
설계는 변경을 위해 존재하고, 변경에는 어떤 식으로든 비용이 발생한다. 훌륭한 설계란 합리적인 비용 안에서 변경을 수용할 수 있는 구조를 만드는 것이다.
상태가 아닌 책임을 중심으로
객체의 상태는 구현에 속하고, 구현은 불안정해서 변하기 쉽다. 상태를 객체 분할의 중심축으로 삼으면 구현 세부사항이 인터페이스에 스며들어, 상태 변경이 인터페이스 변경으로 이어지고 그 인터페이스에 의존하는 모든 객체로 영향이 퍼진다.
반면 객체의 책임은 인터페이스에 속한다. 책임에 초점을 맞추면 상대적으로 변경에 안정적인 설계를 얻는다.
캡슐화란 변경 가능성이 높은 부분(구현)을 객체 내부로 숨기고 상대적으로 안정적인 부분(인터페이스)만 공개해 변경의 여파를 통제하는 추상화 기법이다.
데이터 중심 설계의 문제점
- 너무 이른 시기에 데이터에 관해 결정하도록 강요한다.
- 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정한다.
그 결과 객체의 인터페이스에 구현이 노출되어 캡슐화가 깨지고, 응집도는 낮고 결합도는 높은, 변경에 취약한 설계가 된다. 올바른 객체지향 설계의 무게 중심은 항상 객체의 내부가 아니라 외부에 있어야 하는데, 데이터 중심 설계의 초점은 내부를 향한다.