나는 지금까지 서비스를 개발하기 위해 설계할 때 데이터 위주로 설계했던 것 같다.
필요한 객체들과 그 필드 값들을 미리 생각하고 설계한 뒤 책임을 부여하는 방식이다.
그렇게되면, 고립된 객체의 상태에 초점을 맞추기 때문에 캡슐화를 위반하기 쉽고, 결합도가 높아지며 응집도가 낮아질 확률이 높다.
그럼 코드를 변경하기 어려워지고 유지보수가 힘들어지게 된다.
책 오브젝트에서는 이러한 문제점을 해결하기 위해서는 데이터가 아닌 책임에 초점을 맞추어야 한다고 말한다.
GRSAP 패턴을 사용하면 책임 할당의 어려움을 해결하기 위한 답을 준다고 한다.
책임 주도 설계를 위해서는 다음의 두가지 원칙을 따라야 한다.
- 데이터보다 행동을 먼저 결정하라
- 협력이라는 문맥 안에서 책임을 결정하라
데이터보다 행동을 먼저 결정하라.
객체에게 중요한 것은 데이터가 아니라 외부에 제공하는 행동이다. 즉 객체의 책임이다.
각 객체는 하나의 서비스를 제공하기 위해 협력에 참여하며, 협력 안에서 수행하는 책임이 곧 객채의 존재가치이다.
데이터는 객체가 책임을 수행하는데 필요한 재료를 제공할 뿐이다.
우리는 "이 객체가 수행해야 하는 책임은 무엇인가" 를 결정한 이후 "이 책임을 수행하는데 필요한 데이터는 무엇인가" 를 결정해야한다.
협력이라는 문맥 안에서 책임을 결정하라.
객체에 할당된 책임이 협력에 어울리지 않을 시 해당 책임은 나쁜 것이다.
객체에 할당된 책임이 어색하더라도 협력에 적합할 시 해당 책임은 좋은 것이다.
즉, 책임은 객체의 입장이 아닌 객체가 참여하는 협력에 적합해야 한다.
협력을 시작하는 주체는 메시지 전송자이기 때문에 협력에 적합한 책임이란 메시지 수신자가 아닌, 전송자에게 적합한 책임을 의미한다.
다시 말해 메시지를 전송하는 클라이언트의 의도에 적합한 책임을 할당해야 한다.
👎🏻
난 이전에 클래스를 결정한 뒤 클래스의 책임을 찾아 나서는 대신, 메시지를 결정하고 이 메시지를 누구에게 전송해야 하는지 찾아보았다. 이제는 생각의 전환이 필요한 시점이다.
"이 클래스가 필요한 것은 알겠어, 이 클래스는 무엇을 해야하지 ?" -> "메시지를 전송해야 하는데 누구에게 메시지를 전송해야하지 ?"
객체를 가지고 있기 때문에 메시지를 보내는 것이 아니다. 메시지를 보내야 하기 때문에 객체를 갖게 된 것이다.
메시지가 클라이언트의 의도를 표현한다는 사실에 주목하자. 객체를 결정하기 전 객체가 수신할 메시지를 먼저 결정하는 점을 주목하자.
클라이언트는 어떤 객체가 메시지를 수신할지 알지 못하며, 알 필요가 없다.
그저 임의의 객체가 메시지를 수신할 것이라 믿고, 자신의 의도를 표현할 메시지를 전송할 뿐이다. => 캡슐화
메시지를 수신하기로 결정된 객체는 처리할 책임을 할당받게 된다.
GRASP 패턴
General Responsibility Assignment Software Pattern 의 약자로 객체에게 책임을 할당할 때 지침으로 삼을 수 있는 원칙들을 패턴 형식으로 정리한 것이다.
도메인을 그려보아라
설계를 시작하기 전 도메인에 대한 개략적인 모습을 그려 보는 것이 유용하다.
도메인 안에는 무수한 개념들이 존재하고 책임 할당의 대상으로 사용하면 좀 더 설계가 쉬워진다.
도메인의 개념들의 의미와 관계가 반드시 정확하거나 완벽할 필요는 없다.
이 단계에서는 할당받을 객체들의 종류와 관계에 대한 유용한 정보를 제공할 수 있다면 충분하다.
정보 전문가에게 책임을 할당하라
애플리케이션에 대해 전송된 메시지를 책임질 첫 번째 객체를 선택하는 것으로 설계를 시작한다.
첫 번째 책임을 영화를 예매하는 것이라고 가정해보자. 이 책임을 수행하는데 필요한 메시지를 결정해야 한다.
메시지는 메시지를 수신할 객체가 아니라 메시지를 전송할 객체의 의도를 반영해야 한다고 이전에 말했다.
따라서 첫 번째 질문은 다음과 같다.
"메시지를 전송할 객체는 무엇을 원하는가 ?"
바로 영화를 예매하는 것이다. 따라서 메시지의 이름은 "예매하라"가 적절해 보인다.
두 번째 질문은 다음과 같다.
"메시지를 수신할 객체는 누구인가 ?"
이는 객체가 상태와 행동을 통합한 캡슐화의 단위라는 사실에 집중해야한다.
객체의 책임과 책임을 수행하는데 필요한 상태는 동일한 객체 안에 존재해야한다.
따라서 객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이다.
이 때 정보와 데이터는 다르다. 책임을 수행하는 객체가 정보를 '알고'있다고 해서 정보를 '저장'하고 있을 필요는 없다.
정보를 제공해주는 다른 객체를 알고 있거나, 계산해서 제공할 수도 있기 때문이다.
그럼 '예매하라' 메시지를 처리할 책임은 어느 객체에 할당해야할까 ?
'상영' 이라는 도메인 개념에 적합해보인다.
상영은 영화에 대한 정보와 상영시간, 순번 등 예매에 필요한 정보를 알고 있는 상영에 대한 정보 전문가기 때문이다.
상영(Screening) 에게 예매를 위한 책임을 할당하자.
'예매하라' 메시지를 Screening 이 수신했을 때 수행해야하는 작업의 흐름을 무엇일까 ?
이제부터는 외부 인터페이스가 아닌 Screening 내부로 들어가 처리하기 위해 필요한 절차와 구현을 고민하는 것이다.
너무 세세한 고민은 필요 없다. 단순히 스스로 처리할 수 있고 없는 작업이 무엇인지를 가릴 정도면 충분하다.
스스로 처리할 수 없는 작업은 외부에 메시지를 통해 도움을 받아야 한다. 이로서 협력 공동체가 구성되는 것이다.
'예매하라' 메시지를 완료하기 위해서는 예매 가격을 계산하는 작업이 필요하다.
예매 가격은 영화 한 편의 가격을 알아야 하지만 Screening 은 영화 가격에 대한 정보를 모르기 때문에 외부 객체에게 도움을 요청해야한다. 새로운 메시지의 이름으로는 '가격을 계산하라'가 적절해보인다.
이제 메시지를 책임질 객체를 선택해야한다. 영화 가격을 계산하는데 필요한 정보를 알고 있는 전문가는 당연히 영화일 것이다.
따라서 해당 메시지를 수신할 객체는 Movie 가 될 것이다. Movie 는 영화 가격 계산의 책임을 지게된다.
뒷 과정은 생략하도록 하겠다.
이와 같은 메시지와 메시지를 처리할 정보를 알고 있는 전문가를 찾고 서로 협력하는 흐름으로 설계가 이루어진다.
창조자에게 객체 생성 책임을 할당하라
앞선 영화 예매 협력의 최종 결과물은 Reservation 인스턴스를 생성하는 것이다. 이는 협력에 참여하는 어떤 객체는 Reservation 인스턴스를 생성할 책임을 할당해야 한다는 뜻이다.
GRASP 에서는 책임자 패턴을 사용해 이를 해결한다.
CREATOR 패턴이란
객체 A를 생성해야 할 때 어떤 객체에게 객체 생성 책임을 할당해야 하는가 ? 아래 조건을 최대한 많이 만족하는 B에게 객체 생성을 할당하라.
- B가 A 객체를 포함하거나 참조한다.
- B가 A 객체를 기록한다.
- B가 A 객체를 긴밀하게 사용한다.
- B가 A 객체를 초기화하는데 필요한 데이터를 가지고 있다. (B는 A에 대한 정보 전문가다)
이에 적합한 건 바로 Screening 이다. Screening 은 예매 정보를 생성하는데 필요한 영화, 상영시간, 순번에 대한 정보 전문가이며, 예매 요금을 계산하는데 필수적인 Movie 도 알고 있다. 때문에 Screening 을 Reservation 의 CREATOR 로 선택하는 것이 적절하다.
이후에는 코드 구현으로 설계에 대한 검증을 진행하면 된다.
협력과 책임이 제대로 동작하는지 확인할 수 있는 유일한 방법은 코드를 작성하고 테스트하는 것 뿐이다.
'개인 공부 > Java' 카테고리의 다른 글
[모던 자바 인 액션] 동작 파라미터화 (0) | 2023.11.13 |
---|---|
[오브젝트] 클래스를 분리해야할 때 (0) | 2023.08.08 |
[Java] 제네릭 (0) | 2022.09.15 |