GYUD-TECH

[스터디: 객체지향의 사실과 오해] 역할, 책임, 협력 본문

스터디

[스터디: 객체지향의 사실과 오해] 역할, 책임, 협력

GYUD 2024. 1. 25. 19:02

4. 역할, 책임, 협력

협력

인간은 사회적 동물이라는 말이 있듯이, 사회 속에서 타인과의 협력을 통해서 살아간다.

자신이 처한 상황에 따라서 이상적인 선택을 할 수도 있고, 자신에게 불리한 선택을 할 수 도 있다.

이렇게 협력을 통해 만들어진 상황에 따라 행동이 바뀔 수 있는 것이다.

 

객체 역시 협력에 의해서 행동이 바뀔 수 있다.

따라서, 협력이 먼저 자리잡는다면, 행동이 자연스럽게 결정될 것이고, 행동에 따라 상태가 결정된다.

💡 따라서, 객체의 행동이나 상태가 아니라 객체간의 협력에 집중하여 설계하라.

 

 

1장에서 이야기했던 커피주문의 이야기를 협력의 관점에서 바라보자.

 

커피주문이라는 목적을 달성하기 위해서 A, B, C 세개의 객체가 등장한다.

A는 손님이라는 역할, B는 캐시어라는 역할, C는 바리스타 역할을 부여받고, 각자가 역할에 따른 책임을 수행한다.

 

손님은 커피를 주문하는 책임을 수행한다.

이 책임을 수행하기 위해서 캐시어 역할을 가진 객체에게 커피를 주문 받으라고 요청할 것이다.

바리스타가 아닌 캐시어 역할을 가진 객체에게 요청한 이유는 캐시어 역할은 주문을 받는 책임이 있기 때문이다.

 

캐시어는 손님의 주문을 받는 책임과 동시에, 바리스타에게 커피를 만들어라고 요청하는 책임을 수행한다.

역시나 손님이 아닌 바리스타에게 요청한 이유는 바리스타가 커피를 만드는 책임이 있기 때문이다.

 

커피 주문에 필요한 요청을 요약하면 아래와 같다.

1. 손님이 캐시어에게 커피주문을 받아달라고 요청한다.
2. 캐시어가 바리스타에게 커피를 제조해 달라고 요청한다.

 

협력을 하지 않고 있던 객체가 요청을 통해서 협력을 시작한다.

💡 이러한 요청을 메시지라고 하며, 객체는 메시지를 통해서 서로 협력한다.

 

책임 vs 메시지

책임과 메시지가 똑같은 것일까?

 

책임은 해당 객체가 무엇을 할 수 있는지를 나열한 것이다.

바리스타 역할에는 커피를 제조할 수 있다는 책임이 있다면, 이 역할을 가진 객체는 커피 제조를 할 수 있다는 것을 뜻한다.

따라서 꼭 캐시어가 아닌 친구 객체가 커피 제조를 요청 하더라도, 똑같이 기능을 수행할 것이다.

 

이와 달리 메시지는 송신자와 수신자가 존재하는 협력관계에서 나타날 수 있다.

캐시어가 바리스타에게 커피 제조를 요청할 때와, 친구가 바리스타에게 커피 제조를 요청하는 것은 송신자가 다르기 때문에 서로 다른 메시지라고 볼 수 있다.

 

일반적으로는 하나의 메시지로 책임이 수행되는 것이 아니라, 여러개의 메시지가 합쳐져서 책임이 수행된다.

 


역할

앞의 바리스타 예시에서 협력에 대해 이야기 할 때 "A가 B에게 요청한다" 라고 하지 않고 "손님이 캐시어에게 요청한다" 라고 역할로 표현했다.

 

만약 A가 B에게 요청한다고 정의했다면, 만약 D라는 손님이 등장하면 어떻게 할 것인가?

"D가 B에게 요청한다" 라는 새로운 협력관계를 정의해야만 한다.

 

하지만 역할을 중심으로 "손님이 캐시어에게 요청한다" 라는 메시지를 정의하면 역할을 수행하는 객체가 바뀌더라도 같은 협력관계를 유지할 수 있다.

A와 D라는 객체를 손님이라는 역할로 추상화 함으로써 복잡함을 단순화하고, 유연성과 재사용 성을 높인 것이다.

추상화를 활용하여 역할간의 협력을 정의함으로써
프로그램을 단순화하고, 객체의 유연성과 재사용성을 높인다.

 

 

나는 이제까지 프로그램을 설계할 때 상태를 먼저 정의했었다.

1. 게임에서 캐릭터 정보를 저장할 필요가 있어서 캐릭터 객체를 생성한다.
2. 캐릭터 객체에는 이름이라는 정보가 필요해서 name이라는 상태를 선언한다.
3. 유저 객체에서 캐릭터를 생성하는 기능이 필요하다면 캐릭터 객체에 createByName(name) 메서드를 추가한다.

 

하지만 협력의 관점에서 객체를 바라본다면 순서가 아래와 같이 바뀌어야 한다.

1. 유저 객체가 캐릭터를 이름을 지정하면서 생성하는 기능이 필요하다.
2. 기능 구현을 위해서는 유저가 캐릭터에게 "생성하라" 라는 요청을 보내야 한다.
3. 캐릭터는 생성해라 라는 책임을 수행하기 위해서 createByName(name) 메서드를 가진다.
4. 요청을 수행하기 위해서 name이라는 필드가 캐릭터 객체내에 존재해야 하기 때문에 name이라는 상태를 선언한다.

 

만일 유저가 생성하는 것이 캐릭터가 아니라 펫으로 변경될 가능성이 있다면 Unit 이라는 인터페이스를 생성하여 유저에서 인터페이스를 참조한다면 조금 더 유연한 설계가 가능할 것이다.

 

이렇게 상태가 아닌 협력 속에서의 행동을 생각하고, 이후에 행동에 필요한 상태를 정의하는 것이 바람직하다.

 


역할, 책임, 협력 중심의 설계 방법

역할, 책임, 협력 중심의 설계를 위해서는 어떻게 해야할까?

많은 객체지향 개발자들 역시 이를 고민하였고 이를 정형화한 3가지 방법을 책에서 소개한다.

 

1. 책임-주도 설계

시스템의 기능을 작은 책임으로 분할하고, 책임을 수행할 객체를 찾아 할당하는 "책임 중심적인 설계" 방법이다.

 

2. 디자인 패턴

객체지향설계에 자주 나오는 구조를 쉽게 사용하기 위하여 패턴형식으로 정의 해놓은 것이다.

더보기

책에서 디자인 패턴과 관련하여 Composite 패턴을 소개한다.

(Composite 패턴의 개념에 대해서 소개하는 것은 아니지만, 이해를 위해 읽어보았는데 아래 그림이 이상했다.)


Composite 패턴을 사진을 들어 설명하는데, Leaf 객체는 Component 객체 하위에 있는데 Operation() 메서드만 가지고 있는것이 이해가 되지 않아서 추가로 내용을 찾아보았다.


https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EB%B3%B5%ED%95%A9%EC%B2%B4Composite-%ED%8C%A8%ED%84%B4-%EC%99%84%EB%B2%BD-%EB%A7%88%EC%8A%A4%ED%84%B0%ED%95%98%EA%B8%B0

 

💠 복합체(Composite) 패턴 - 완벽 마스터하기

Composite Pattern 복합체 패턴(Composite Pattern)은 복합 객체(Composite) 와 단일 객체(Leaf)를 동일한 컴포넌트로 취급하여, 클라이언트에게 이 둘을 구분하지 않고 동일한 인터페이스를 사용하도록 하는

inpa.tistory.com

 

스터디 원들과도 함께 이야기를 나누면서, 그림에서 Component 클래스를 잘못 표기한 것 같다고 결론지었다.

 

Composite 패턴이 이 장에서 중요한 것은 아니지만, 패턴을 통해서 자연스럽게 구현되는 일반화/특수화 관계를 이해하는데 도움이 되었던 것 같다.

 

 

3. 테스트 주도 개발

테스트 주도 개발은 테스트를 작성함으로써, 코드의 오류를 줄이는 것이 목적이라고만 생각했다.

하지만 테스트를 작성하기 위해서 객체가 어떤 책임을 수행하는지, 어떤 결과를 반환하는지를 고민하는 과정에서 자연스럽게 협력과 책임을 고려하고 있었다는 것을 알 수 있었다.

 


 

프로젝트를 진행하면서 항상 상태 중심으로 설계를 해오던 나에게 협력 중심의 설계방식은 신선하게 다가왔다.

 

요즘 객체의 분리 및 캡슐화에 초점을 맞추어서 객체를 설계하는 것을 공부하고 있는데, 협력 중심의 설계를 활용하면 객체의 역할을 분명히 하고, 책임을 정확하게 할당하는 것이 자연스럽게 가능할 것 같다.

 

이 내용을 적용하여 해피에이징 프로젝트에서 설계한 객체들을 변경해보면서, 객체지향적 설계를 연습하고 이 과정을 기록할 예정이다.