✏️, 💡,❓ 해당 이모지는 저의 생각임을 나타냅니다.
데이터 중심은 협력이라는 문맥을 벗어나 고립된 객체의 상태에 초점을 맞춰서 캡슐화를 위반하고, 결합을 높이고, 코드를 변경하기 어려워진다.
책임에 초점을 맞춰 설계할 때 어려운 것은 어떤 객체에게 어떤 책임을 할당할지 결정하기 어렵다는 것이다.
이번 장에서는 GRASP 패턴에 대해 알아볼 것이다. GRASP 패턴은 책임 할당의 어려움을 해결하기 위한 답을 제시해 줄 것이다.
01 책임 주도 설계를 향해
책임 중심 설계를 위해서는 다음 두 가지 원칙을 따라야 한다.
- 데이터보다 행동을 먼저 결정하라
- 협력이라는 문맥 안에서 책임을 결정하라
데이터보다 행동을 먼저 결정하라
객체는 협력에 참여하기 위해 존재하며 협력 안에서 수행하는 책임이 객체의 존재가치를 증명한다.
데이터는 객체가 책임을 수행하는 데 필요한 재료를 제공할 뿐이다.
데이터 중심에서는 "이 객체가 포함해야 하는 데이터가 무엇인가"를 결정한 후에 "데이터를 처리하는 데 필요한 오퍼레이션은 무엇인가"를 결정한다.
반면 책임 중심 설계에서는 "이 객체가 수행해야 하는 책임은 무엇인가"를 결정한 후에 "이 책임을 수행하는 데 필요한 데이터는 무엇인가"를 결정한다.
협력이라는 문맥 안에서 책임을 결정하라
객체에게 할당된 책임의 품질은, 협력에 적합한 정도로 결정된다.
객체에게 할당된 책임이 협력에 어울리지 않는다면 그 책임은 나쁜 것이다. 반면 객체의 입장에서 책임이 조금 어색해보이더라도 협력에 적합하다면 그 책임은 좋은 것이다. 책임은 객체의 입장이 아니라 객체가 참여하는 협력에 적합해야 한다.
협력을 시작하는 주체는 메시지 전송자이기 때문에 협력에 적합한 책임이란 메시지 전송자에게 적합한 책임을 의미한다.
= 메시지를 전송하는 클라이언트의 의도에 적합한 책임을 할당해야 한다는 것이다.
✏️ 이는 자칫 '클라이언트가 많아질 경우를 대비하기 어렵지 않나'라는 의문을 불러일으킬 수도 있으나, '메시지'라는 필요성을 먼저 생각해야한다는 뜻에 가깝다.
협력에 적합한 책임을 수확하기 위해서는 객체를 결정한 후에 메시지를 선택하는 것이 아니라 메시지를 결정한 후에 객체를 선택해야 한다. 메시지가 존재하기 때문에 메시지를 처리할 객체가 필요한 것이다.
메시지를 먼저 결정하기 때문에 메시지 송신자는 메시지 수신자에 대한 어떤 가정도 할 수 없다. 그래서 메시지 수신자는 깔끔하게 캡슐화된다.
책임 주도 설계
책임 주도 설계의 흐름을 다시 나열해보자.
- 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악한다.
- 시스템 책임을 더 작은 책임으로 분할한다.
- 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당한다.
- 객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다.
책임 주도 설계의 핵심은 책임을 정한 후에 책임을 수행할 객체를 결정하는 것이다.
02 책임 할당을 위한 GRASP 패턴
GRASP는 "General Responsibility Assignment Software Pattern" (일반적인 책임 할당을 위한 소프트웨어 패턴)의 약자이다.
도메인 개념에서 출발하기
설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용하다.

위 그림은 영화 예매 시스템(챕터 2)을 구성하는 도메인 개념과 관계를 대략적으로 표현한 것이다. (* 정확하거나 완벽할 필요는 없다.)
정보 전문가에게 책임을 할당하라
사용자에게 제공해야 하는 기능은 영화를 예매하는 것이다. (이 애플리케이션엔 영화를 예매할 책임이 있다.) 이제 이 책임을 수행하는 데 필요한 메시지를, 메시지를 전송할 객체의 의도를 반영해서 결정해야 한다.
메시지를 전송할 객체는 무엇을 원하는가?
협력을 시작할 객체는 미정이지만 이 객체가 원하는 것은 영화를 예매하는 것이므로, 메시지 이름으로는 예매하라가 적절하다.
메시지를 수신할 적합한 객체는 누구인가?
객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이다. (정보 전문가 패턴 - 정보를 알고 있는 객체만이 책임을 어떻게 수행할지 스스로 결정할 수 있다.)
여기서 이야기하는 정보는 데이터와 다르다는 사실에 주의해라. 책임을 수행하는 객체가 정보를 '알고'있다고 해서'저장'하고 있을 필요는 없다. 객체는 해당 정보를 제공할 수 있는 다른 객체를 알고 있거나 필요한 정보를 계산해서 제공할 수 있다.
영화를 예매하는 데 필요한 정보를 가장 많이 알고 있는 객체는 아마 '상영'일 것이다.
예매하라 메시지를 완료하기 위해서는 예매 가격을 계산하는 작업이 필요하다. 예매 가격은 영화에 예매 인원수를 곱해야 하는데, 상영은 가격 계산에 필요한 정보를 모르기 때문에 외부 객체에게 도움을 요청해야 한다. ('가격을 계산하라')
가격을 계산하는데 필요한 정보를 알고 있는 전문가는 영화이다. 영화에서는 요금을 계산하기 위해 할인 가능 여부를 판단해야 하기 때문에 '할인 여부를 판단하라'는 메시지를 전송한다. 그리고 할인 조건 객체가 이 메시지를 책임진다.
높은 응집도와 낮은 결합도
설계는 트레이드오프 활동이다.
방금 설계에서 영화 대신 상영이 직접 할인 조건 객체와 협력하도록 하면 어떨까?
그렇게 하더라도 기능적으로는 동일하다. 하지만 우리는 응집도와 결합도 때문에 영화에서 할인 조건 객체와 협력하는 방식을 선택했다.
LOW COUPLING 패턴 (낮은 결합도 패턴)
어떻게 하면 의존성을 낮추고 변화의 영향을 줄이며 재사용성을 증가시킬 수 있을까?
여러 설계 대안들이 있을 때 낮은 결합도를 유지할 수 있는 설계를 선택하라.
위 설계에서 영화는 이미 할인 조건 목록을 속성으로 포함하고 있다. 그래서 할인 조건과 협력하게 했을 때 설계 전체적으로 결합도를 추가하지 않고도 협력을 완성할 수 있다. (이미 알고 있기 때문에)
HIGE COHESION 패턴 (높은 응집도 패턴)
어떻게 복잡성을 관리할 수 있는 수준으로 유지할 것인가?
여러 설계 대안들이 있을 때 높은 응집도를 유지할 수 있는 설계를 선택하라.
상영의 가장 중요한 책임은 예매를 생성하는 것이다. 만약 할인 조건과 협력해야 한다면, 상영은 영화 요금 계싼과 관련된 책임 일부를 떠안아야 할 것이다. 그리고 예매 요금을 계산하는 방식이 변경될 경우 상영도 함께 변경해야 한다.
💡 객체의 가장 중요한 책임을 생각해보자.
창조자에게 객체 생성 책임을 할당하라
영화 예매 협력의 최종 결과물은 Reservation (예매) 인스턴스를 생성하는 것이다. 이것은 협력 참여 객체 중 누군가는 Reservation 인스턴스를 생성할 책임을 할당해야 한다는 것이다.
CREATOR 패턴
객체 A를 생성하는 객채 생성 책임은 누구에게 할당해야 하는가? 아래 조건을 최대한 많이 만족하는 B에게 할당하라.
- B가 A객체를 포함하거나 참조한다.
- B가 A객체를 기록한다.
- B가 A객체를 긴밀하게 사용한다.
- B가 A 객체를 초기화하는 데 필요한 데이터를 가지고 있다 (B는 A에 대한 정보 전문가다)
CREATOR 패턴의 의도는 어떤 방식으로든 생성되는 객체와 관련될 필요가 있는 객체에 해당 객체를 생성할 책임을 맡기는 것이다.
03 구현을 통한 검증
응집도가 낮다는 것은 서로 연관성이 없는 기능이 하나의 클래스 안에 뭉쳐져 있다는 것을 의미한다. 응집도가 초래하는 문제를 해결하기 위해서는 변경의 이유에 따라 클래스를 분리해야 한다.
일반적으로 설계를 개선하는 작업은 변경의 이유가 하나 이상인 클래스를 찾는 것으로부터 시작하는 것이 좋다.
1. 인스턴스 변수가 초기화되는 시점을 살펴본다.
응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화한다. 반면 응집도가 낮은 클래스는 객체의 속성 중 일부만 초기화하고 일부는 초기화하지 않는다.
2. 메서드들이 인스턴스 변수를 사용하는 방식을 살펴본다.
메서드들이 사용하는 속성에 따라 그룹이 나뉜다면 클래스의 응집도가 낮다고 볼 수 있다. 이 경우 속성 그룹과 해당 그룹에 접근하는 메서드 그룹을 기준으로 코드를 분리해야 한다.
책임 중심으로 구현하고 난 후의 설계는 이렇다.

도메인 구조와 비슷한 것을 알 수 있다. 도메인 모델은 코드 구조에도 영향을 미친다. 여기서 강조하고 싶은 곳은 변경 역시 도메인의 일부라는 것이다. 도메인에서 변하는 개념과 이들 사이의 관계가 투영돼 있어야 한다.
코드의 구조가 바뀌면 도메인에 대한 관점도 바뀐다.
04 책임 주도 설계의 대안
책임 주도 설계에 익숙해지기 위해선 부단한 노력과 시간이 필요하다.
책임과 객체 사이에서 방황할 때 돌파구를 찾기 위해 선택하는 방법은 최대한 빠르게 목적한 기능을 수행하는 코드를 작성하는 것이다.
주의할 점은 코드를 수정한 후 겉으로 들어나는 동작이 바뀌어서는 안 된다는 것이다. 동작을 바꾸지 않고 내부 구조를 변경하는 것은 리팩터링이라고 부른다.
메서드 응집도
긴 메서드는 다양한 측면에서 코드의 유지보수에 부정적 영향을 미친다.
- 어떤 일을 수행하는지 한눈에 파악하기 어려워 코드 이해에 많은 시간이 걸린다.
- 하나의 메서드 안에서 많은 작업을 처리해 변경이 필요할 때 수정이 필요한 부분을 찾기가 어렵다.
- 로직의 일부만 재사용하는 것이 불가능하다.
- 코드 중복을 초래하기 쉽다.
긴 메서드는 응집도가 낮아 이해하기도 재사용하기도 변경하기도 어렵다. 마이클 페더스는 이런 메서드를 몬스터 메서드라고 부른다.
응집도가 낮은 메서드는 로직의 흐름을 이해하기 위해 주석이 필요하다. 주석을 추가하는 대신 메서드를 분해해 각 메서드의 응집도를 높이는 게 좋다.
객체를 자율적으로 만들자
자신이 소유하고 있는 데이터를 자기 스스로 처리하도록 만드는 것이 자율적인 객체를 만드는 지름길이다. 따라서 메서드가 사용하는 데이터를 저장하고 있는 클래스로 메서드를 이동시키면 된다.
여기서 하고 싶은 말은, 책임 주도 설계 방법에 익숙하지 않다면 일단 데이터 중심으로 구현한 후 리팩터링 해도 유사한 결과를 얻을 수 있다는 것이다.
처음부터 책임 주도 설계 방법을 따르는 것보다 동작하는 코드를 작성한 후 리팩터링하는 것이 더 훌륭한 결과물을 낳을 수도 있다.
마무리하며
객체의 입장에서 책임이 조금 어색해보이더라도 협력에 적합하다면 그 책임은 좋은 것이다,라는 말이 무척 중요해 보인다.
협력에서 책임을 바라봐야 한다.
그리고 데이터 중심으로 설계를 했더라도 조금씩 책임 중심으로 보려고 하고, 코드를 수정하는 행위가 꼭 필요한 것 같다
'Book' 카테고리의 다른 글
[오브젝트] 챕터 07: 객체 분해 (2) | 2025.06.04 |
---|---|
[오브젝트] 챕터 06: 메시지와 인터페이스 (0) | 2025.05.21 |
[오브젝트] 챕터 04: 설계 품질과 트레이드오프 (0) | 2025.05.04 |
[오브젝트] 챕터 03: 역할, 책임, 협력 (0) | 2025.05.02 |
[오브젝트] 챕터 02: 객체지향 프로그래밍 (0) | 2025.04.22 |