본문 바로가기

기술서적 정리/오브젝트 - 코드로 이해하는 객체지향 설계

03 CHAPTER 역할, 책임, 협력

https://wikibook.co.kr/object/

 

오브젝트: 코드로 이해하는 객체지향 설계

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라! 객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두 번째 걸음은 객체를

wikibook.co.kr

 

객체지향의 본질은 협력하는 객체들의 공동체를 창조하는 것이다.

 

클래스와 상속은 객체들의 책임과 협력이 어느 정도 자리를 잡은 후에 사용할 수 있는 구현 메커니즘일 뿐이다.

 

애플리케이션의 기능을 구현하기 위해 어떤 협력이 필요하고 협력을 위해 어떤 역할과 책임이 필요한지를 고민하지 않은 채 너무 이른 시기에 구현에 초점을 맞추는 것은 변경하기 어렵고 유연하지 못한 코드를 낳는 원인이 된다.




01 협력

협력

두 객체 사이의 협력은 하나의 객체가 다른 객체에게 도움을 요청할 때 시작된다.

 

메시지 전송은 객체 사이의 협력을 위해 사용할 수 있는 유일한 커뮤니케이션 수단이다.

 

객체는 다른 객체의 상세한 내부 구현에 직접 접근할 수 없기 때문에 오직 메시지 전송을 통해서만 자신의 요청을 전달할 수 있다.

 

객체가 메시지를 처리할 방법을 스스로 선택한다는 점이 중요하다.

객체가 자신의 일을 스스로 처리할 수 있는 자율적인 존재라는 것을 의미한다.

 

객체를 자율적으로 만드는 가장 기본적인 방법은 내부 구현을 캡슐화하는 것이다.

캡슐화를 통해 변경에 대한 파급효과를 제한할 수 있기 때문에 자율적인 객체는 변경하기도 쉬워진다.



자율적인 객체는 자신에게 할당된 책임을 수행하던 중에 필요한 정보를 알지 못하거나 외부의 도움이 필요한 경우 적절한 객체에게 메시지를 전송해서 협력을 요청한다.

 

객체들 사이의 협력을 구성하는 일련의 요청과 응답의 흐름을 통해 애플리케이션의 기능이 구현된다.




 

협력이 설계를 위한 문맥을 결정한다.

애플리케이션 안에 어떤 객체가 필요하다면 그 이유는 단 하나여야한다.

그 객체가 어떤 협력에 참여하고 있기 때문이다.

 

협력은 객체가 필요한 이유와 객체가 수행하는 행동의 동기를 제공한다.

 

객체의 행동을 결정하는 것이 협력이라면 객체의 상태를 결정하는 것은 행동이다.

 

객체의 상태는 그 객체가 행동을 수행하는 데 필요한 정보가 무엇인지로 결정된다.

 

상태는 객체가 행동하는 데 필요한 정보에 의해 결정되고 행동은 협력안에서 객체가 처리할 메시지로 결정된다.

결과적으로 객체가 참여하는 협력이 객체를 구성하는 행동과 상태 모두를 결정한다.

따라서 협력객체를 설계하는 데 필요한 일종의 문맥을 제공한다.






02 책임

책임이란 무엇인가.

협력에 참여하기 위해 객체가 수행하는 행동을 책임이라고 부른다.

 

객체의 책임은 객체가 ‘무엇을 알고 있는가’와 ‘무엇을 할 수 있는가’로 구성된다.

 

하는 것.

객체를 생성하거나 계산을 수행하는 등의 스스로 하는 것.

다른 객체의 행동을 시작시키는 것.

다른 객체의 행동을 제어하고 조절하는 것.

 

아는 것.

사적인 정보에 관해 아는 것.

관련된 객체에 관해 아는 것.

자신이 유도하거나 계산할 수 있는 것에 관해 아는 것.

 

객체의 구현 방법은 상대적으로 책임보다는 덜 중요하며 책임을 결정한 다음에 고민해도 늦지 않다.

객체를 어떻게 구현할 것인가가 아니라 객체에게 어떤 책임을 할당할 것인가에 집중하는 것이 중요하다.

 

 

책임 할당

자율적인 객체를 만드는 가장 기본적인 방법은 책임을 수행하는 데 필요한 정보를 가장 잘 알고 있는 전문가에게 그 책임을 할당하는 것이다.

이를 책임 할당을 위한 INFORMATION EXPERT(정보 전문가) 패턴이라고 부른다.

 

객체는 협력에 필요한 지식과 방법을 가장 잘 알고 있는 객체에게 도움을 요청한다.

요청에 응답하기 위해 필요한 이 행동객체가 수행할 책임으로 이어지는 것이다.



정보 전문가에게 책임을 할당하는 방법

1. 객체가 책임을 수행하게 하는 유일한 방법은 메시지를 전송하는 것이므로 책임을 할당한다는 것은 메시지의 이름을 결정하는 것과 같다.

 

2. 메시지를 선택했으면 메시지를 처리할 적절한 객체를 선택해야 한다.

정보를 가장 많이 알고 있는 객체에게 책임을 할당한다.

정보를 소유하고 있거나 해당 정보의 소유자를 가장 잘 알고 있는 전문가가 이에 해당한다.



위의 방법처럼 객체지향 설계는 1)협력에 필요한 메시지를 찾고 2)메시지에 적절한 객체를 선택하는 반복적인 과정을 통해 이뤄진다.

그리고 이런 메시지가 메시지를 수신할 객체의 책임을 결정한다.

 

이렇게 결정된 메시지가 객체의 퍼블릭 인터페이스를 구성한다.

 

책임 할당기본적인 전략은 책임을 수행할 정보 전문가를 찾는 것이다.

정보 전문가에게 책임을 할당하는 것만으로도 상태와 행동을 함께 가지는 자율적인 객체를 만들 가능성이 높아지기 때문이다.



책임 주도 설계

책임을 찾고 책임을 수행할 적절한 객체를 찾아 책임을 할당하는 방식으로 협력을 설계하는 방법을 책임 주도 설계(Responsibility-Driven Design, RDD)라고 부른다.

 

책임 주도 설계 방법의 과정

1. 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악한다.

2. 시스템 책임을 더 작은 책임으로 분할한다.

3. 분할된 책임을 수행할 수 있는 적절한 객체 또한 역할을 찾아 책임을 할당한다.

4. 객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다.

5. 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 한다.

 

 

책임 주도 설계는 자연스럽게 객체의 구현이 아닌 책임에 집중할 수 있게 한다.

구현이 아닌 책임에 집중하는 것이 중요한 이유는 유연하고 견고한 객체지향 시스템을 위해 가장 중요한 재료가 바로 책임이기 때문이다.

 

책임을 할당할 때 고려해야하는 두 가지 요소.

1. 메시지가 객체를 결정한다.

2. 행동이 상태를 결정한다.





메시지가 객체를 결정한다.

메시지가 객체를 선택하게 해야 하는 두 가지 중요한 이유.

 

1. 객체가 최소한의 인터페이스를 가질 수 있게된다.

꼭 필요한 크기의 퍼블릭 인터페이스를 가질 수 있다.

 

2. 객체는 충분히 추상적인 인터페이스를 가질 수 있게 된다.

메시지는 외부의 객체가 요청하는 무언가를 의미하기 때문에 메시지를 먼저 식별하면 무엇을 수행할지에 초점을 맞추는 인터페이스를 얻을 수 있다.

 

객체가 충분히 추상적이면서 미니멀리즘을 따르는 인터페이스를 가지게 하고 싶다면 메시지가 객체를 선택하게 하라.




행동이 상태를 결정한다.

객체를 객체답게 만드는 것은 객체의 상태가 아니라 객체가 다른 객체에게 제공하는 행동이다.

 

캡슐화를 위반하지 않도록 구현에 대한 결정을 뒤로 미루면서 객체의 행위를 고려하기 위해서는 항상 협력이라는 문맥 안에서 객체를 생각해야 한다.

 

협력 관계 속에서 다른 객체에게 무엇을 제공해야 하고 다른 객체로부터 무엇을 얻어야 하는 지를 고민해야만 훌륭한 책임을 수확할 수 있다.

 

중요한 것은 객체의 상태가 아니라 행동이다.

상태는 행동을 결정하고 나서야 비로소 결정할 수 있다.

협력이 객체의 행동을 결정하고 행동이 상태를 결정한다.

그리고 그 행동이 바로 객체의 책임이 된다.



 

03 역할

역할과 협력

객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합역할이라고 부른다.

실제로 협력을 모델링할 때는 특정한 객체가 아니라 역할에게 책임을 할당한다고 생각하는 것이 좋다.

 

어떤 이유로 역할이라는 개념을 이용해서 설계과정을 더 번거롭게 만드는 것일까?

어차피 역할이 없어도 객체만으로 충분히 협력을 설계할 수 있는 것 아닌가?



유연하고 재사용 가능한 협력

역할이 중요한 이유는 역할을 통해 유연하고 재사용 가능한 협력을 얻을 수 있기 때문이다.

 

두 종류의 객체가 참여하는 협력을 개별적으로 만든다면 코드가 중복되고 말 것이다.

프로그래밍에서 코드 중복은 모든 문제의 근원이기 때문에 이런 방법은 피해야 한다.

 

객체를 교대로 바꿔 끼울 수 있는 일종의 슬롯 이 슬롯이 바로 역할이다.

 

역할은 여러 종류의 구체적인 객체를 포괄하는 추상화이다.

 

책임과 역할을 중심으로 협력을 바라보는 것이 바로 변경과 확장이 용이한 유연한 설계로 나아가는 첫걸음이다.



 

참고.

추상 클래스 :
책임의 일부를 구현해 놓은 것
역할을 수행할 수 있는 모든 객체들이 공유하는 상태와 행동의 기본 구현이 존재할 경우 추상 클래스로 구현.

인터페이스 :
일체의 구현 없이 책임의 집합만을 나열해 놓은 것.
공통의 구현이 필요없고 단지 책임의 목록만 정의하면 될 때 인터페이스로 구현.

 

 

객체 vs 역할

많이 생각했던 의문인데 오직 한 종류의 객체만 협력에 참여하는 상황에서 역할이라는 개념을 고려하는 것이 유용할까?

 

역할이라는 개념을 생략하고 직접 객체를 이용해 협력을 설계하는 것이 더 좋지 않을까?

 

이런 경우에는 역할을 사용하는 것은 상황을 오히려 더 복잡하게 만드는 것은 아닐까?



위 질문들에 대한 답변은 협력은 적합한 책임을 수행하는 대상이 한 종류라면 간단하게 객체로 간주한다.

만약 여러 종류의 객체들이 참여할 수 있다면 역할이라고 부르면 된다.



설계 초반에는 적절한 책임과 협력의 큰 그림을 탐색하는 것이 가장 중요한 목표여야 하고 역할과 객체를 명확하게 구분하는 것은 그렇게 중요하지는 않다.

애매하다면.

1. 단순하게 객체로 시작하고.

2. 반복적으로 책임과 협력을 정제해가면서.

3. 필요한 순간에 객체로부터 역할을 분리해내는 것이 가장 좋은 방법이다.



협력을 지속적으로 정제하다 보면 두 협력이 거의 유사한 구조를 보인다는 것을 발견하게 될 것이다.

이 경우 두 협력을 하나로 합치면서 두 객체를 포괄할 수 있는 역할을 고려해서 객체를 역할로 대체할 수 있다.

 

다양한 객체들이 협력에 참여한다는 것이 확실하다면 역할로 시작하라.

하지만 모든 것이 안개 속에 둘러싸여 있고 정확한 결정을 내리기 어려운 상황이라면 구체적인 객체로 시작하라.





역할과 추상화

역할은 공통의 책임을 바탕으로 객체의 종류를 숨기기 때문에 이런 관점에서 역할을 객체의 추상화로 볼 수 있다.

 

추상화의 첫 번째 장점은 세부 사항에 억눌리지 않고도 상위 수준의 정책을 쉽고 간단하게 표현할 수 있다는 것이다.

추상화를 적절하게 사용하면 불필요한 세부 사항을 생략하고 핵심적인 개념을 강조할 수 있다.

구체적인 조합을 고려하지 않고도 상위 수준에서 협력을 설명할 수 있다.

상위 수준에서 협력을 설명하면 구체적인 객체들이 가지는 복잡성을 제거하고 단순화해서 표현할 수 있다.

역할이 중요한 이유는 동일한 협력을 수행하는 객체들을 추상화할 수 있기 때문이다.

역할을 사용하면 복잡하게 객체가 하는 행동을 말할 필요가 없이 줄여서 표현이 가능하다.



추상화의 두 번째 장점은 설계를 유연하게 만들 수 있다.

협력 안에서 동일한 책임을 수행하는 객체들은 동일한 역할을 수행하기 때문에 서로 대체 가능하다.

따라서 역할은 다양한 환경에서 다양한 객체들을 수용할 수 있게 해주므로 협력을 유연하게 만든다.

협력 안에서 역할이라는 추상화를 이용하면 기존 코드를 수정하지 않고도 새로운 행동을 추가할 수 있다.

프레임 워크나 디자인 패턴과 같이 재사용 가능한 코드나 설계의 아이디어를 구성하는 핵심적인 요소가 바로 역할이다.





배우와 배역

특정한 협력 안에서는 협력에 필요한 객체의 특정한 역할을 제외한 나머지 부분은 감춰질 것이다.

객체는 다수의 역할을 보유할 수 있지만 객체가 참여하는 특정 협력은 객체의 한 가지 역할만 바라볼 수 있다.

 

객체는 다양한 역할을 가질 수 있다.

객체는 협력에 참여할 때 협력 안에서 하나의 역할로 보여진다.

객체가 다른 협력에 참여할 때는 다른 역할로 보여진다.

협력의 관점에서 동일한 역할을 수행하는 객체들은 서로 대체 가능하다.

역할은 특정한 객체의 종류를 캡슐화하기 때문에 동일한 역할을 수행하고 계약을 준수하는 대체 가능한 객체들은 다형적이다.