역할, 책임, 협력
- 객체 지향 패러다임의 핵심은 역할Role, 책임Responsibility, 협력Collaboration이다.
- 객체 지향의 본질은 협력 객체들의 공동체를 창조하는 것이다.
- 객체 지향 설계의 핵심은 객체를 적절한 책임을 부여하여 좋은 협력 관계를 구축하는 것이다.
객체 지향 어플리케이션의 제어 흐름
- 하나의 객체에 통제되는 것이 아니라, 다양한 객체들에게 균형있게 역할이 부여되어 있어야 한다.
- 요청 흐름에 따라서 객체는 보유한 로직을 실행하며 전체 기능에 도달한다.
협력과 책임, 그리고 역할
한 작업을 수행하기 위해서 객체들이 상호작용 하는 것을 협력이라고 말한다. 협력하는 객체는 수행하는 로직 범위 안의 책임을 지게되며, 책임들이 모여 하나의 역할을 가진다.
- 유저의 정보를 작성하고 수정하는 UserInfo 객체가 있다.
- UserInfo 객체는 다른 객체들에서 유저 정보 작성, 수정을 요청받는다. → 협력
- UserInfo 객체는 요청 받은 기능을 수행하기 위해 로직을 실행한다. → 책임
- UserInfo 객체는 유저의 정보를 작성하고 수정한다. → 역할
협력
메시지 전송
객체 간에 협력을 위해 사용하는 커뮤니케이션 수단
객체 지향 패러다임을 따른다면, 객체는 하나의 책임을 지게 된다. 그렇기 때문에 스스로에게 해당하지 않는 기능을 수행하기 위해서는 다른 객체에 ‘협력’을 구해야 한다. 이를 메시지 전송이라고 말한다. 메시지 전송에 응답한 객체는 메시지에 해당하는 요청을 수행한다.
객체의 자율성
플레이어의 Status를 관리하는 Status 객체와 플레이어의 움직임을 관리하는 Move 객체가 있다.
Move 객체는 플레이어의 이동 속도를 가져와 움직임을 결정한다. 단순히 Public으로 Status를 공개하고 있다면, Move 객체는 Status 객체에서 정보를 가져와 Move 객체에서 계산할 수 있다.
그러면, Move 객체는 일방적으로 Status 객체에서 데이터만 가져와 로직을 수행하게 되는 것이다. 이런 경우, Status 객체는 자율성이 낮은 상태이며, 이는 수동적이라고 말한다.
캡슐화
객체의 자율성을 살리는 가장 기본적인 방법은 구현을 캡슐화하는 것이다. 위에서 예시를 가져와 생각해보자.
Status 객체에서 Status를 담당하는 변수 명이 변경되거나, 전달되는 데이터에 한 번 별도의 처리가 있어야 한다면? Move 객체는 올바른 값을 받을 수 없다. 캡슐화는 외부로부터 정보를 보호하는 기법이면서 정확한 정보를 전달할 수 있도록 하는 기법이다.
책임
객체가 협력을 위해 수행하는 행동을 책임이라고 한다. 크레이그 라만은 객체의 책임은 하는 것과 아는 것으로 세분화했다.
하는 것과 아는 것
하는 것
- 객체를 생성하거나 계산을 수행하는 것
- 다른 객체의 행동을 시작 시키는 것
- 다른 객체의 활동을 제어하고 조작하는 것
아는 것
- 객체 스스로에 필요한 정보를 아는 것
- 연관 되어있는 객체에 관해서 아는 것
- 자신이 유도하거나 계산할 수 있는 것을 아는 것
책임과 메시지의 크기는 다르다.
메시지가 단순하다고 하여, 책임이 단순한 것은 아니다. 또, 메시지가 복잡하다고 하여 책임이 복잡한 것은 아니다. 그리고 책임은 객체가 수행하는 활동을 종합적으로 서술하는 것이므로, 추상적이며 메시지의 개념보다 크다.
객체 지향 설계에서 중요한 것은 책임
객체에게 적절한 책임을 부여하는 것이 설계의 품질을 좌우한다.
어떤 기준으로 책임을 부여하나?
책임을 수행하는 데에 적절한 정보를 가지고 있는 객체에게 할당하는 것이 올바르다. 단순히 말장난과 같은 이야기다.
Status의 연산을 수행하는 기능이 필요하다면, 가장 적절한 정보를 가지고 있는 객체가 어디일까? Status 객체다. 정보를 수집하고 변경하는 책임뿐만 아니라, 연산을 수행하는 책임도 동일하게 부여해줄 수 있다.
책임을 부여하는 것은 요청받은 협력에 대해 가장 많은 데이터를 담고 있는 객체에 전달하면 된다.
책임 주도 설계
협력 설계를 위해서 책임에 초점을 맞추는 것을 책임 주도 설계RDD라고 부른다.
메시지가 객체를 결정한다
객체에 책임을 할당하기 위해서 메시지를 선별하고 처리할 객체를 후에 선택하는 것이다. 이는 객체가 어떠한 범위 내에 있는 기능을 수행하는 것이 아니라, 기능이 필요한 경우 수행할 수 있는 객체를 판별하라는 의미로 보인다.
이렇게 되면 두개의 이점이 생긴다.
- 객체가 최소한의 인터페이스를 가진다.
- 필요한 메시지를 파악할 때 마다 메서드를 보유할 수 있다.
- 충분히 추상적인 인터페이스를 가진다.
- 객체의 인터페이스는 무엇을What 하는지는 표현해야 한다. 하지만, 어떻게How 수행하는지 노출해선 안된다.
- 이는 메서드가 어떤 작업을 수행하는지 노출할 수 있지만, 내부 처리 로직을 수행하는 것은 숨겨야 한다는 것이다. → 은닉
행동이 상태를 결정한다.
- 객체는 협력 참여를 위해 존재한다.
- 객체는 협력에 필요한 행동을 제공할 수 있어야 한다.
- 객체의 정체성은 다른 객체에 제공하는 행동이다.
- 행동은 객체가 협력에 참여하는 유일한 방법이다.
- 적절한 객체임을 판단하는 방법은 적절한 책임을 할당했냐는 것이다.
- 적절한 책임은 협력이 얼마나 적절한가이다.
객체지향 패러다임에 입문한 사람들의 초기 실수는 객체의 상태에 집중하는 것이다. 그렇게 된다면, 객체의 상태에 필요한 행동을 결정한다. 이러한 방법은 객체 내부 구현이 Public으로 구현되어 캡슐화를 저하한다. 이렇게 객체의 상태에 맞춘 설계 방법을 데이터 주도 설계Data-Driven Design이라고 부른다.
개별 객체의 상태와 행동이 아니라, 필요한 기능을 구현하기 위해 초점을 맞춘다면, 응집도가 높고 결합도가 낮은 객체를 창조할 수 있다. 상태는 객체가 정상적인 행동을 수행하기 위해 필요한 재료일 뿐이다.
역할
역할과 협력
객체의 목적은 책임의 집합으로 표시할 수 있다. 이러한 책임의 집합을 객체의 역할이라 부른다. 특정 객체에 협력을 추가한다고 생각하는 것보다 어떤 역할에 협력을 추가한다고 생각하자.
유연하고 재사용 가능한 협력
역할은 유연하고 재사용 가능한 협력을 얻을 수 있게 한다. Dialogue를 출력하는 시스템이 있다고 가정해보자. Dialogue 출력 시스템에는 선택지를 제공하는 Dialogue가 있으며, 선택지를 제공하지 않는 Dialogue가 있다.
선택지 제공 여부에 따른 차이가 존재하지만, 두 Dialogue는 출력을 위해서 출력하기 위한 정보들을 Dialogue 출력 시스템에 제공한다는 공통된 책임을 수행한다.
이 때, 객체를 바라보지 말고 선택지를 제공한다는 책임만 생각해보자. 두 객체의 공통된 협력을 책임지는 대표 객체가 있다면, 수월하게 헤쳐나갈 수 있을 것이다. 이러한 대표 객체가 역할이다.
역할, 협력, 책임 같은 단어보다 간단한 단어로 이야기한다면, 공통된 부분을 책임지는 객체를 만들어서 불필요한 중복 코드를 제거함으로서, 코드의 유연성과 추상화를 제공할 수 있을 것이다.
객체 대 역할
역할은 객체가 참여할 수 있는 일종의 슬롯이다. 그런데, 오직 한 종류의 객체만 협력에 응답할 수 있는 상황에서 역할을 고려하는 것이 올바른가? 협력을 설계하는 것이 더 좋지 않을까 생각이 들 수 있다.
레베카 워프스브록은 ‘협력에 참여하는 후보가 여러 종류의 객체에 의해 수행될 필요가 있다면 그 후보는 역할이 되지만, 단지 한 종류 객체만이 협력에 참여할 필요가 있다면 후보는 객체가 된다’고 했다.
결론적으로, 협력에 응답할 수 있는 책임을 가진 대상이 한 종류라면 객체며, 여러 종류의 객체들이 참여할 수 있다면 역할이라 부른다.
트리그비 린스카우는 협력, 역할, 객체, 클래스를 다음과 같이 말했다.
- 협력은 역할들의 상호작용으로 구성된다.
- 협력을 구성하기 위해 역할에 적합한 객체가 선정된다.
- 객체는 클래스를 이용해 구현되고 생성된다.
객체와 역할을 같은 동일 선상에 두지 말고, 조금 더 개념적인 부분으로 사고를 확장할 필요가 있을 것 같다.
역할 모델링(Role Modeling)
트리그비 린스카우가 제안한 역할을 설계의 중심으로 보는 개념이다. 상호작용하는 객체들의 협력 패턴을 역할 사이의 협력 패턴으로 추상화함으로서 유연하고 재사용 가능한 시스템을 얻을 수 있는 방법에 관해서 저서에서 서술한 바 있다.