대부분의 책에서 객체지향은 역할, 책임, 협력이 중요하다고 한다.
도대체 역할, 책임, 협력은 정확히 객체세계에서 어떤 부분을 이루고 있을까? 한번 알아보자.
협력
객체지향 시스템은 자율적인 객체들이 공동체를 이루고 있는 것을 말한다.
객체지향 세계에서 협력이란 기능을 구현할 수 있는 유일한 방법이다.
두 객체사이의 협력은 하나의 객체가 다른 객체에게 도움을 요청할 때 사용한다.
백문이 불여일타라고 코드로 한번 보는 것이 빠를 것 이다.
Lotto Class 는 자신의 IntArray 를 랜덤 숫자로 채우기 위해서 randomLottoNumberGenerator 에 협력을 요청하고 있다.
그림으로 그리면 아래와 같을 것이다.
Lotto Class 는 LottoNumeberGenerator 의 generateNumbers 메소드를 호출하여 협력을 요청한다.
Lotto 는 LottoNumeberGenerator 에게 랜덤한 6자리의 숫자를 만드는 것을 완전히 위임하며 협력을 요청하고 있다.
이제 Flow 를 한번 처음부터 차근차근 해석해보면
Lotto 는 LottoNumeberGenerator 에게 도와달라는 메세지를 보냈다.
LottoNumeberGenerator 는 메세지를 받고 처리할 방법을 선택해서 처리한 뒤 보내준다.
여기서 중요한 점은 선택한다는 것이다.
결국 객체의 상태와 행동을 정의하기 위해서는
해당 객체가 어느 협력에 참여하고 있는가를 잘 확인하여야 한다.
책임
책임이란 객체 세계에서 협력에 참여하기 위해 객체가 수행하는 역할을 뜻한다.
크레이그 라만은 객체가 "하는 것" 과 "아는 것" 으로 책임을 크게 분류해야 한다고 말했다.
하는 것
- 객체를 생성하거나 계산을 수행하는 등의 스스로 하는 것.
- 다른 객체의 행동을 시작시키는 것
- 다른 객체의 활동을 제어하고 조절하는 것
아는 것
- 사적인 정보에 관해 아는 것
- 관련된 객체에 관해 아는 것
- 자신이 유도한거나 계산할 수 있는 것에 관해 아는 것
아까 적어 놓았던 Lotto 클래스의 책임은 무엇인가?
이를 위해 큰 도식도에서 한번 Lotto 클래스가 어떤 위치에 있는지 살펴보자.
Lotto 클래스는 Market 이 소유하고 있다.
Lotto 의 요금을 알 수 있는 "fee(): Int" 메소드는 Market 에서 CalculateChange(money) 메소드에서
티겟 요금을 알기위해 Lotto 에게 협력을 보낼때 이용될 것 이다.
계산해보고 충족된다면 직원(Employee) 이(가) Lotto 를 Market 에서 꺼낼수 있을 것이다.
그럼 여기서 Lotto 가 "하는 것", "아는 것" 으로 구분해서 책임을 적어보자.
하는 것
- fee(): Int 메소드를 통해서 사적인 정보인 가격을 알려 줌
- numbers(): IntArrary 메소드를 통해서 사적인 정보인 여섯가지 숫자 정보를 알려 줌.
- 랜덤한 6자리 수를 받기 위해 RandomLottoNumberGenerator 에 협력을 요청함.
아는 것
- 가격정보(fee) 를 사적인 정보로 가지고 있음.
- 여섯자리 숫자 리스트(numbers) 를 사적인 정보로 가지고 있음.
- RandomLottoNumberGenerator 에 generateNumbers() 를 요청하면 6자리 수를 준다는 걸 알고 있음.
객체는 자신이 해야할 역할을 알고 있어야 하는 책임이 있다.
위의 도식도를 보았을 때 Lotto 의 책임은 가격을 저장하는 것과, 여섯자리 수를 저장하고 제공하는 역할이다.
또한 그래서 자신이 랜덤한 6자리 수를 만들수 없기에
RandomLottoNumberGenrator 가 generateNumbers() 를 통해 6자리 수를 준다는 것도 알고 있어야 한다.
즉 책임이 있어야만 잘 협력할 수 있는 객체가 된다. 반대로 말하면 객체들 간의 협력을 잘 구축해야만
책임이 잘 분리된 객체 시스템을 설계할 수 있을 것 이다.
책임할당
글을 읽다보면 객체에게 적절한 책임을 할당하는게 중요하다는 사실을 알게 될 것이다.
보통 사회에서는 우리는 책임을 할당하는데 있어서 그 분야의 전문가에게 할당한다.
간단히 예를들어 건물의 설계도를 그려야 한다면 건축설계사한테 책임을 할당하는 것과 같다고 생각하면 된다.
객체 세계에도 전문가에게 할당하는 것이 있는데 그것을 INFORMATION EXPERT PATTERN 이라고 한다.
만약 우리가 외부에서 Lotto 를 구입할 수 있도록 해줘야 하는 시스템을 구축해야 한다면
외부의 클라이언트가 Lotto 를 살 수 있는 협력체계를 구성해야 할 것 이다.
협력을 하기 위해서는 일단 메세지가 필요하다.
외부에서 Lotto 를 사기 위해서는 "로또를 구입한다" 라는 메세지가 필요할 것이다.
그럼 이 메세지는 누가 처리할 수 있을까?
우리가 처리할 수 있도록 책임을 분배해 줘야 할 것이다.
아마 로또를 판매하는 사람은 가게에 일하는 직원이 가장 전문가일 것 이다.
그렇다면 직원에게 책임을 할당해 보자.
이렇게 객체 지향 설계는 협력에 필요한 메세지를 생성하고
메세지를 처리할 전문가 객체에게 책임을 분배하는 방식이 반복되며 설게 된다.
여기서 한가지 중요한 점은 "메세지가 객체를 결정한다는 점" 이다.
즉, 협력에 필요한 메세지를 먼져 만들고, 그를 처리할 수 있는 객체에게 할당 했다.
따라서 이로 얻을 수 있는 이점은 아래와 같다.
1. 객체가 최소한의 인터페이스만 가진다.
- 메세지를 처리할 인터페이스만 가지게 되므로 최소한의 인터페이스만 가지게 될 수 밖에 없다.
2. 추상적인 인터페이스를 가질 수 있게 된다.
- 메세지를 처리하기 위해 어떤 인터페이스를 제공해야 하는지 알고 있으므로 추상화시킬 수 있다.
로또 구매 시스템의 경우 클라이언트에게 로또를 구매할 수 있는 인터페이스를 제공 하면 되므로
"purchase" 또는 "purchaseLotto" 인터페이스를 제공하면 될 것이다.
역할
어떤 객체가 특정한 협력 속에서 수행하는 책임의 집합을 역할이라고 부른다.
실제 협력을 모델링 할때는 특정한 객체가 아니라 역할에 할당한다고 이해하는 것이 더 좋다.
예를 들어 아까의 예제를 다시 가져와보자.
우리는 employee 객체에게 할당한 것 처럼 느낄 수도 있지만.
사실 "로또를 팔 수 있는 협력을 제공 하는 역할" 에 "employee" 라는 객체를 고른 것이다.
즉, 우리는 기본적으로 "역할" 을 먼져 생각한 것이다.
기본적으로 바로 객체를 할당하는 건 안좋은 설계로 이어질 수 있다.
그 이유를 한번 알아보자.
우리의 기본적인 흐름도가 위와 같다고 해보자.
구입 -> 계산 -> 할인정책 계산 으로 가는 구조이다.
문제가 보이는가?
ADiscoutPolicy 에서 악취가 난다고 생각할 수 있을 것이다.
왜 이런 문제가 발생했을까?
사실 저 부분은 할인 정책이 무엇인지 구하는 것이지, 특정 할인정책에 대해서 보는 것이 아니다.
그렇다면 저 부분을 역할로 다시 바꿔보자.
바로 객체를 할당하는 것이 아닌 역할로 할당하자 설계가 더 유연해진 모습이다.
우리가 객체를 설계할때 사실 "이 객체가 어떤 역할을 하는가?" 에 대한 질문이 먼져 선행되어야 하는 이유이다.
느낀점
오늘은 조영호님의 오브젝트 책을 읽으면서
내가 짜던 코드에 대한 영감을 많이 받았다.
기존에 Lotto 를 객체 시스템으로 설계하고 있었는데 어떻게 구조를 개선해야 할지 또 깨달았다.
기존 구매 플로우
책을 읽고나서 변한 플로우
일단 기본적으로 계산하는 과정을 Market 에서 분리했다.
계산은 POS 기를 통해서 직원이 입력하면 포스기가 계산하는 구조로 변경했고
마켓은 금액에 대한 정보만을 일단 가지고 있다.
로또 머신은 로또를 생산해 내는 역할을 가지고 있고, 마켓은 협력을 요청할 수 있다.
이렇게 되니 마켓은 사실상 가게에 대한 정보를 구성하는 형태로 변경되어 내가 떠올리던 세계와 맞아 떨어졌다.
이 글을 읽고나서 꼭 간단하게라도 도식을 그리고 코드를 짜는 역할을 해보자.
이책은 조영호님의 오브젝트를 보고 정리한 글입니다.
http://www.yes24.com/Product/Goods/74219491
'Java' 카테고리의 다른 글
[Spring Batch] Batch Info Table 수동 생성하기 (0) | 2021.12.12 |
---|---|
객체지향 체조 (0) | 2021.11.28 |
JPA PersistContext (0) | 2021.11.22 |
Java Stream (0) | 2021.11.11 |
젠킨스 JVM 메모리 설정 (0) | 2021.10.20 |