'협력'의 중요성
객체지향의 세계에서 중요한 것은 개별의 객체가 아닌 객체들 사이에 이루어지는 '협력'이다. 훌륭한 객체지향은 각각의 객체에 집중할 때가 아닌 객체들끼리 상호작용을 이루며 협력적인 객체를 생성해낼 때 객체지향 설계의 품질이 올라간다.
요청과 응답으로 이루어지는 협력
작가는 앨리스 이야기의 파이를 훔친 하트 잭에 대한 재판 과정을 통해 객체간의 협력 과정을 설명한다. 왕과 하얀 토끼, 모자 장수는 각각의 '요청'을 하고, 그 요청에 '응답'하면서 서로 협력한다. 이 등장인물들이 요청을 받아들일 수 있는 이유는 그 요청에 대해 적절한 방식으로 응답하는데 필요한 행동방식과 지식을 지니기 때문이고, 이러한 요청과 응답은 객체가 수행할 책임을 정의한다.
책임
앨리스의 이야기에서 등장인물들은 각각의 요청에 응답할 책임을 지닌다. 재판을 수행하라는 요청에 재판을 수행할 책임을, 증인을 불러오라는 요청에 증인을 불러올 책임을 지니게 되는 것처럼, 객체지향의 세계에서는 어떤 객체가 요청에 대해 대답해줄 수 있거나, 적절한 행동을 할 의무가 있는 경우 해당 객체가 책임을 가진다고 말한다. 그러므로 객체지향 개발에서는 책임을 능숙하게 소프트웨어 객체에 할당하는 것이 중요하다고 할 수 있다.
책임은 크게 '하는 것'과 '아는 것'으로 분류할 수 있다. 예를 들어, 재판 과정의 이야기에서 하얀 토끼는 관련된 객체에 대해 '아는 것'과 다른 객체의 행동을 '시작시키는 것'의 두 가지 종류의 책임을 지닌다.
책임은 언제나 객체지향의 설계에서 품질을 결정하는 가장 중요한 요소이다. 명확한 책임의 부여가 애플리케이션을 미래를 결정짓는다.
객체가 다른 객체에게 주어진 책임을 수행하도록 요청보내는 것을 '메시지 전송'이라고 한다. 객체간의 협력이 메시지를 통해 이루어지는 것이다. 메시지를 통해 협력에 참여하는 두 객체 사이의 관계를 정의할 수 있다. '재판하라', '증언하라'와 같은 메시지를 통해 두 객체 사이의 협력이 가능해지는 것이다.
하지만 메시지와 책임을 같은 수준에서 바라보는 것은 위험하다. 실제 객체지향에서 책임을 결정한 후에는 실제로 협력을 정제하면서 메시지로 변환 시 보통 하나의 책임이 여러 메시지로 분할된다. 설계 시작 초반에는 어떤 객체가 어떤 책임을 가지고 어떤 방식으로 서로 협력해야 하는지에 대한 개요 정도만 아는것으로도 충분하다.
역할
어떤 객체가 수행하는 책임의 집합은 객체가 협력 안에서 수행하는 역할을 의미한다. 역할은 재사용 가능하고 유연한 객체지향 설계를 낳는 매우 중요한 구성요소이기에 이 또한 객체지향에 있어서 굉장히 중요한 개념이라고 할 수 있다.
앞서 말한 앨리스 이야기의 재판 과정에서, 동일한 재판과정에서 왕이 '여왕'으로, 증인이 모자 장수가 아닌 '요리사'와 '앨리스'로 대체되는 이야기가 등장한다. 각각의 협력은 등장인물만 다를뿐 다른 것이 다 동일하기에 '하나의 협력'으로 추상화 할 수 있다. 이것이 '역할(role)'을 사용하는 이유이다.
즉, 역할은 해당 역할을 수행할 수 있는 어떤 객체라도 대신할 수 있다고 말하는 것과 같다. 하지만, 아무 객체나 역할을 대체할 수 있는건 아니다. 오직 동일한 메시지를 이해할 수 있는 객체만 동일한 역할을 수행할 수 있다. 역할은 유사한 협력을 추상화해서 인지 과부화를 줄일 수 있기 때문에 객체지향 설계의 단순성, 유연성, 재사용성을 보장해주는 핵심 개념이다.
객체의 모양을 결정하는 협력
많은 객체지향 프로그래밍을 처음 접하는 사람들이 객체 존재의 이유를 데이터를 저장하기 위한것이라는 선입견을 가지는 경우가 많다. 하지만 객체가 존재하는 이유는 행위를 수행하며 협력에 참여하기 위해서이며, 중요한 것은 객체의 행동, 책임이다.
이런 선입견을 가지게 되는 이유는 '협력'에 집중하지 않고 각 객체를 독립적으로 바라보기 때문이다. 각 객체에만 집중해서 각 객체를 잘 구현해내는데에만 집중하고 객체간의 협력은 고려하지 않게된다면, 좋은 객체지향 설계를 이루어낼 수가 없다.
좋은 협력을 이루어내는 설계를 한다는 것은 객체들이 주고받을 요청과 응답의 흐름을 결정하는 것을 의미한다. 이러한 흐름은 객체가 협력에 참여하기 위한 책임이 된다. 그리고 이 책임은 객체 지향 프로그래밍에서 객체가 외부에 제공할 행동이 된다. 이렇게 책임을 먼저 결정하고 난 후에 어떤 데이터가 필요하며, 어떻게 구현해낼지를 고민하는 것이 올바른 객체지향 설계의 구현 순서라고 할 수 있다.
즉, 올바른 객체지향 설계란 객체의 상태와 행위에 대해 고민하기 전에 객체의 '협력'을 정의하고, 그 협력 안에서 충분히 자율적이며 동시에 충분히 협력 가능한 객체를 창조하는 것이다.
객체지향 설계 기법
역할, 책임, 협력이 객체지향 설계에 있어 가장 중요한 기본 토대라는 사실을 앞의 과정들을 통해 확인하였다. 이 세 가지 중요한 기반을 바탕으로, 주요한 애플리케이션 설계 방법 세 가지가 있다.
책임 주도 설계는 협력에 필요한 책임을 식별하고 적합한 객체에 책임을 할당하는 방식으로, 객체지향 설계의 가장 근본적인 토대라고 할 수 있다. 방법에 대해 좀 더 세부적으로 설명하자면, 시스템이 사용자에게 제공해야 하는 시스템 책임을 파악하고, 이 책임을 더 작은 책임으로 분할한다. 그리고 책임을 수행할 적합한 객체를 찾아 할당 후, 객체들이 서로 협력하게 하는 것이다.
디자인 패턴은 전문가들이 반복적으로 사용하는 해결 방법을 정의해놓은 설계 템플릿의 모음이다. 이미 패턴을 정의해놓았기 때문에 반복횟수를 줄일 수 있다는 장점이 있다. 여기서 말하는 '패턴'이란 특정한 상황에서 설계를 돕기 위해 모방하고 수정할 수 있는 과거의 설계 경험을 말한다. 디자인 패턴의 가장 유명한 예로는 COMPOSTIE 패턴이 있다. 디자인 패턴을 활용한다면 책임-주도 설계의 절차를 순차적으로 따르지 않아도 객체들의 역할과 책임, 협력 관계를 빠르고 손쉽게 포착할 수 있을 것이다.
테스트 주도 개발은 먼저 테스트를 작성하고 테스트를 통과하는 구체적인 코드를 추가하면서 애플리케이션을 완성해나가는 방식을 따른다. 이는 설계를 위한 기법이지, 테스트를 위한 기법이 아니라는 것에 유의해야 한다. 조금 더 세부적으로 설명하자면, 실패하는 테스트를 작성하고, 테스트를 통과하는 가장 간단한 코드를 작성한 후, 리팩터링을 통해 중복을 제거하는 것이다. 이 기법 역시 책임-주도 설계의 기본 개념을 따르면서, 목적지에 '테스트'라는 안전장치를 통해 좀 더 빠르고 견고한 방법으로 도달할 수 있게 해준다.