역할, 책임, 협력
《오브젝트: 코드로 이해하는 객체지향 설계》의 ‘역할, 책임, 협력’ 파트를 읽고 정리한 내용입니다.
객체 지향 프로그래밍을 이해하기 위해 가장 중요한 것은 무엇일까? 클래스, 상속, 지연 바인딩 등 일까? 이러한 것들은 다분히 구현 측면에 치우쳐 있기 때문에 객체지향 패러다임의 본질과는 거리가 멀다.
역할, 책임, 협력
객체 지향의 본질은 협력하는 객체들의 공동체를 창조하는 것👏🏻
객체지향 설계의 핵심은 협력을 구성하기위해 적절한 객체를 찾고 적절한 책임을 할당하는 과정에서 들어난다.
어떤 협력이 필요하고 협력을 위해 어떤 역할과 책임이 필요한지를 고민하지 않은 채 구현에만 초점을 맞추면 변경하기 어렵고 유연하지 못한 코드를 낳게 된다.
협력 (Collaboration)
협력이란 어떤 객체가 다른 객체에게 무언가를 요청하는 것이다. (…) 협력을 설계할 때는 객체를 서로 분리된 인스턴스가 아닌 협력하는 파트너로 인식해야 한다. - Rebecca Wirfs-Brock, 《오브젝트 디자인: 소프트웨어 개발의 성공 열쇠》
객체들은 기능을 구현하기 위해 메시지를 주고 받으면서 상호작용하게 된다. 이처럼 객체들이 애플리케이션의 기능을 구현하기 위해 수행하는 상호작용을 협력이라고 한다. 객체가 협력에 참여하기 위해 수행하는 로직은 책임이라고 부른다. 객체들이 협력 안에서 수행하는 책임들이 모여 객체가 수행하는 역할을 구성한다.
메시지 전송 (message sending)
협력운 객체지향의 세계에서 기능을 구현할 수 있는 유일한 방법이다. **메시지 전송(message sending)**은 객체 사이의 협력을 위해 사용할 수 있는 유일한 커뮤니케이션 수단이다.
메시지를 수신한 객체는 메서드를 실행해 요청에 응답한다. 객체는 메시지를 처리할 방법을 스스로 선택한다. 외부의 객체는 오직 메시지만 전송할 수 있으며, 어떻게 처리할 지는 객체가 직접 결정한다. (객체를 자율적으로 만드는 가장 기본적인 방법은 내부 구현을 캡슐화하는 것이다.)
설계를 위한 문맥 (context)
객체란 상태와 행동을 함께 캡슐화하는 실행 단위다. 그렇다면 객체가 가질 수 있는 상태와 행동을 어떤 기준으로 결정해야 할까?
애플리케이션 안에 어떤 객체가 필요하다면 그 이유는 하나. 그 객체가 어떤 협력에 참여하고 있기 때문이다. 객체가 협력에 참여할 수 있는 이유는 협력에 필요한 적절한 행동을 보유하고 있기 때문이다.
즉, 객체의 행동을 결정하는 것은 객체가 참여하고 있는 협력이다. 협력은 객체가 필요한 이유와 객체가 수행하는 행동의 동기를 제공한다.
객체의 행동을 결정하는 것이 협력이라면 객체의 상태를 결정하는 것은 행동이다. 객체의 상태는 그 객체가 행동을 수행하는 데 필요한 정보가 무엇인지로 결정된다. 결과적으로 객체가 참여하는 협력이 객체를 구성하는 행동과 상태를 모두 결정한다. 협력은 객체를 설계하는 데 필요한 일종의 **문맥(context)**를 제공한다.
책임 (Responsibility)
협력에 참여하기 위해 객체가 수행하는 행동을 책임이라고 부른다. 책임이란 객체에 의해 정의되는 응집도 있는 행위의 집합으로 ‘하는 것(doing)‘과 ‘아는 것(knowing) 으로 구성된다.
객체지향 개발에서 가장 중요한 능력은 책임을 능숙하게 소프트웨어 객체에 할당하는 것 - Craig Larman, 《UML과 패턴의 적용》
책임은 객체지향 설계의 핵심이다. 협력이 중요한 이유는 객체에게 할당할 책임을 결정할 수 있는 문맥을 제공하기 때문이다. 적절한 협력이 적절한 책임을 제공하고, 적절한 책임을 적절한 객체에게 할당해야만 단순하고 유연한 설계가 가능하다.
책임 할당
자율적인 객체를 만드는 가장 기본적인 방법은 책임을 수행하는 데 필요한 정보를 가장 잘 알고 있는 전문가에게 그 책임을 할당하는 것 (Information expert 패턴)
- 시스템이 사용자에게 제공하는 기능을 시스템이 담당할 하나의 책임으로 바라보고 협력(문맥)을 정의한다.
- 시스템이 택임을 완료하는 데 필요한 더 작은 책임을 찾아내고 이를 객체들에게 할당하는 과정을 반복한다.
할당의 기본적인 전략은 책임을 수행할 정보 전문가를 찾는 것. 정보 전문가에게 책임을 할당하는 것만으로도 상태와 행동을 함께 가지는 자율적인 객체를 만들 가능성이 높아짐)
책임 주도 설계 (Responsibility-Driven Design)
협력을 설계하기 위해 책임에 초점을 맞춰야 하고, 어떤 책임을 선택하느냐가 전체적인 설계의 방향과 흐름을 결정한다. 이처럼 책임을 갖고 책임을 수행할 객체를 찾아 책임을 할당하는 방식으로 협력을 설계하는 방법을 책임 주도 설계라고 부른다.
책임 주도 설계는 자연스럽게 객체의 구현이 나닌 책임에 집중할 수 있게 한다. 책임에 집중하는 것이 중요한 이유는 유연하고 견고한 객체지향 시스템을 위해 가장 중요한 재료가 바로 책임이기 때문이다.
메시지가 객체를 결정
메시지를 먼저 식별하고 메시지를 처리할 객체를 나중에 선택한다. 즉, 객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하게 해야한다. 두가지 중요한 이유가 있다.
- 객체가 최소한의 인터페이스를 가질수 있게 된다.
- 객체는 충분히 추상적인 인터페이스를 가질 수 있게 된다. (how가 아닌 what만을 표현해야 한다.)
객체가 충분히 추상적이면서 미니멀리즘을 따르는 인터페이스를 가지게 하고 싶다면 메시지가 객체를 선택하게 하라
행동이 상태를 결정
객체의 행동은 객체가 협력에 참여할 수 있는 유일한 방법이다. 협력에 적합한지를 결정하는 것은 상태가 아니라 행동이다. 객체에 필요한 상태가 무엇인지 결정하면 객체의 내부 구현이 객체의 퍼블릭 인터페이스에 노출되도록 만들기 때문에 캡슐화를 저해한다.
캡슐화를 위한하지 않도록 객체의 행위를 고려하기 위해서는 항상 협력이라는 문맥 안에서 객체를 생각해야 한다. 다른 객체에게 무엇을 제공해야 하고 다른 객체로부터 무엇을 얻어야 하는지 고민해야만 훌륭한 책임을 얻을 수 있다. 상태는 단지 객체가 행동을 수행하기 위해 필요한 재료일 뿐이다. 행동이 바로 객체의 책임이 된다.👊🏻
역할 (Role)
객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합을 역할이라고 부른다. 협력을 모델링할 때는 특정 객체가 아니라 역할에게 책임을 할당한다고 생각하는게 좋다.
유연하고 재사용 가능한 협력
역할이 중요한 이유는 역할을 통해 유연하고 재사용 가능한 협력을 얻을 수 있기 때문이다. 특정 메시지에 응답할 수 있는 대표자를 생각한다면 두 협력을 하나로 통합할 수 있다. 이 대표자를 협력 안에서 두 종류의 객체를 교대로 바꿔 끼울 수 있는 일종의 슬롯으로 생각할 수 있다. 이 슬롯이 역할이다.
역할의 구현하는 가장 일반적인 방법은 추상클래스와 인터페이스를 사용하는 것이다. 추상 클래스는 책임의 일부를 구현해놓은 것이고 인터페이스는 일체의 구현 없이 책임의 집합만을 나열해 놓았다는 차이가 있다.
객체 vs. 역할
여러 종류의 객체에 의해 수행될 필요가 있다면 ⇒ 역할, 한 종류의 객체만이 협력에 참여할 필요가 있다면 ⇒ 객체
대부분의 경우에 어떤 것이 역할이고 어떤 것이 객체인지가 또렷하게 드러나지는 않을 것이다. 설계 초반에는 적절한 책임과 협력의 큰 그림을 탐색하는 것이 가장 중요한 목표여야 하고 역할과 객체를 명확하게 구분하는 것은 그렇게 중요하지 않다. ⇒ 객체로 시작하고 필요한 순간에 객체로부터 역할을 분리