# 들어가며
본 글은 객체지향의 사실과 오해: 역할, 책임, 협력 관점에서 본 객체지향 (opens new window)의 챕터 1~2를 읽고 정리한 내용입니다.
글을 읽어가며 실제 배포를 진행하고 있는 저의 프로젝트 코드를 리팩토링 하는 경험으로 이어지길 기대하며 글을 시작합니다.
# 1. 협력하는 객체들의 공동체
객체지향을 역할, 책임, 협력의 관점에서 이해를 시도한다. 목적 달성을 위한 협력에 있어 특정 역할을 맡고 역할에 적합한 책임을 수행한다는 것은 몇 가지 중요한 개념을 제시한다. 이를 카페의 손님 - 계산원 - 바리스타에 빗대어 설명하면 아래와 같다.
# 1-1. 역할과 책임
- 여러 사람이 동일한 역할을 수행할 수 있다. 이는 역할이란 대체 가능성을 가리킨다. - 손님 입장에서 캐시어는 대체 가능하다.
- 책임 수행의 방법은 자율적으로 선택 가능하다. - 커피 제조 방법은 바리스타마다 달라질 수 있다. 요청에 대한 응답을 다르게 할 수 있는 능력을 다형성이라고 한다.
- 한 사람이 여러 역할을 수행할 수도 있다.
사용자가 최종적으로 인식하게 되는 시스템 기능은 객체들이 성실히 협력해서 일궈낸 결실이다. 어플리케이션 기능은 더 작은 책임으로 분할되고 책임은 적절한 역할을 수행할 수 있는 객체에 의해 수행된다.
따라서 객체지향 설계라는 예술은 적절한 객체에게 적절한 책임을 할당하는 것에서 시작된다. 역할은 관련성 높은 책임의 집합이다.
협력이라는 대전제 아래 객체는 두 가지 덕목을 갖춰야 한다.
- 충분히 협력적이어야 한다. 모든 기능을 스스로 처리하는 객체는 내부 복잡도로 인해 자멸한다.
- 충분히 자율적이어야 한다. 다른 객체의 요청에 대해 스스로 판단하고 행동하는 자율적인 존재여야 한다. 손님은 캐시어에게 바리스타를 통한 주문의 전달 방식에 대해 왈가왈부 할 수 없는 것과 동일하다.
# 1-2. 상태와 행동
객체가 협력에 참여하기 위해 특정 행동을 해야 한다면 그 행동을 하는 데 필요한 상태도 함께 지니고 있어야 함을 의미한다. 커피 제조를 하는 바리스타가 제조법을 알아야 하는 것과 동일하다.
이 과정에서 객체의 사적 부분은 외부에서 간섭할 수 없도록 차단해야 한다.
- 객체는 다른 객체가 무엇을 수행하는 지는 알 수 있다.
- 객체는 다른 객체가 어떻게 수행하는 지는 알 수 없다.
객체가 자율적이라는 의미는 특정 행동 수행방법을 스스로 결정할 수 있어야 한다는 것을 의미한다. 객체는 상태와 행위를 하나의 단위로 묶는 자율적인 존재이다.
# 1-3. 메시지와 메서드, 자율성
객체지향에서는 객체 간 의사소통을 위해 단 한가지 수단만 존재한다. 이를 메세지라고 한다. 다른 객체로의 요청을 메세지 전송이라고 하고, 다른 객체로부터의 요청을 받는 것을 메세지 수신이라고 한다.
메세지 전송 객체를 송신자(sender), 메세지 수신 객체를 수신자(receiver)라고 한다.
이때 객체가 수신된 메시지를 처리하는 방법을 메서드(method)라고 부른다. 역할과 책임 예시에서 볼 수 있듯 손님의 커피 제조 요청에 대해 커피가 어떻게 제조될 지에 대한 자세한 방법, 즉 어떻게(how)에 대한 방법론이 메서드이다.
메시지와 메서드가 분리될 수록 객체의 자율성은 높아진다. 커피 제조 요청이 커피 제조 방법을 결정하는 것은 바람직하지 않다. 이는 캡슐화(encapsulation) 개념과도 깊게 관련되어 있다.
# 1-4. 객체지향의 본질
# 2. 이상한 나라의 객체
# 2-1. 앨리스 예화
이상한 나라의 앨리스 이야기를 예화로 든다. 앨리스는 이야기 내에서 무언가를 먹거나 행동을 취할때 키가 커지고 작아지는 모습을 보여준다. 이러한 이야기를 객체에 빗대면 이와 관련된 개념들에 대해 다음과 같이 설명을 시도해볼 수 있다.
- 특정 시점에서의 앨리스의 상태는 특정 시점에서의 앨리스의 키를 의미한다.
- 앨리스의 키를 변화시키는 것은 앨리스의 행동이다. (상태의 변화를 메서드가 촉진)
- 행동의 결과를 결정하는 것은 상태이다. 행동 이전의 상태에서 행동을 통해 특정 상태로 변화하기 때문에 행동의 결과는 상태에 의존적이다. (키가 100cm에서 특정 행위를 통해 200cm로 커졌다면, 200cm라는 행동의 결과는 이전 상태였던 100cm에 의존적인 형태이다.)
- 행동의 결과가 상태에 의존적이기 때문에, 특정 목표에 도달하기 위해서는 행동의 순서가 중요하다.
- 앨리스는 키가 작아지거나 커지거나에 상관 없이 유일한 존재로 식별 가능하다.
상태라는 개념은 런타임에 불필요한 행동 결과의 이력들을 보관할 필요를 없게 만들어준다. 절차 지향에서는 모든 행동의 이력들을 통해 특정 결과를 도출해야하는데, 메서드 실행 단계에서 과거 상태에서 나타난 행동의 결과값인 현재 상태를 조사해보는 것으로 메서드 성공 여부를 예측할 수 있게 된다.
# 2-2. 상태와 프로퍼티
원시타입과 같이 단순한 값들은 객체가 아니며, 다른 객체 특성을 표현하는 데 사용된다. 물론 객체를 설명하는 특성으로 또 다른 객체가 사용될 수도 있다.
앨리스가 마시는 음료는 단순한 값이 아니고 객체이다. 음료를 설명하는 내부 특성값이 존재하기 때문이다. (음료의 양) 객체의 상태를 구성하는 구성하는 모든 특징을 통틀어 프로퍼티(property)라고 한다.
앨리스가 음료를 모두 마셔 키 상태값이 변경되고, 더 이상 음료가 필요하지 않게 되면 두 객체 사이의 연결은 불필요해진다. 이와 같이 객체 간 의미 있는 연결을 링크(Link)라고 한다. 객체는 링크를 통해서만 메시지를 주고받을 수 있다.
객체의 어트리뷰트는 객체를 구성하는 단순한 값을 의미한다. 반면 프로퍼티는 객체를 구성하는 단순한 값인 어트리뷰트 + 다른 객체를 가리키는 링크의 조합으로 나타낼 수 있다.
정리하면 프로퍼티는 어트리뷰트를 포함하는 개념이며 하나의 객체는 프로퍼티로 구성된다. 이때 다른 객체와의 링크가 없는 독립적 객체라면 어트리뷰트로만 구성된 객체인 것이다.
# 2-3. 행동
객체간 자율성의 보장은 객체 상태의 변경을 스스로에 의해서만 이루어지는 것으로부터 시작된다. 외부로부터 특정 객체 상태값을 변경하기 위해 직접 변경하는 방식이 아닌 간접적 변경 방식이 필요한데, 이것이 행동이다.
객체 행동에 의해 객체 상태가 변경된다는 것은 행동이 부수 효과(side effect)를 초래한다는 것을 의미한다. 부수 효과를 통해 객체의 행동을 상태 변화 관점에서 서술하게 된다.
- 케이크를 먹으면 앨리스의 키가 작아진다. 케이크를 먹으면 케이크의 양이 줄어든다. 케이크를 먹어 앨리스의 키와 케이크의 양이 줄어드는 부수효과가 발생했다.
- 앨리스가 문을 통과하면 앨리스의 위치가 변경된다. 문을 통과하여 앨리스의 위치가 달라지는 부수효과가 발생했다.
위의 예시로부터 행동과 상태는 아래와 같은 관계를 갖는 것을 알 수 있다.
- 객체의 행동은 상태에 영향을 받는다. (앨리스의 키가 크면 작은 문을 통과하는 행동을 취할 수 없다.)
- 객체의 행동은 상태를 변경시킨다. (케이크를 먹어 앨리스의 키가 커진다.)
이는 상태를 통해 행동을 두 가지 관점으로 서술할 수 있다는 것을 의미한다.
- 행동이 현재 상태에 어떤 방식으로 의존하는가? (키가 작아야만 문을 통과할 수 있다는 조건으로 의존)
- 행동이 어떻게 현재의 상태를 변경시킬 예정인가? (케이크를 먹으면 키가 작아져야 한다, 실질적 동작)
객체 자신의 상태의 변화는 자신과 연결되어 있는 또 다른 객체의 상태도 변경시킬 수 있다. 엄밀히 말하면 직접 상태를 변경하는 것이 아니라, 협력하는 다른 객체에 대한 메세지를 전송하여 상태값 변경을 촉진할 수 있는 것이다.
따라서 객체의 행동은 두 가지 관점에서의 부수효과를 갖는다.
- 객체 자신에 대한 상태 변경
- 협력하는 객체로의 메시지 전송 (최종적으로는 협력하는 객체의 상태가 변경)
# 2-4. 상태 캡슐화
객체지향 세계에서의 모든 객체는 스스로가 상태를 관리해야 한다. 즉 앨리스의 세계에서 음료를 마시면 음료의 양이 줄어드는 모습으로 수동적이지만, 객체지향 세계에서는 음료 자신이 양을 직접 줄이는 방법으로 구현되어야 한다는 것이다. 앨리스는 음료를 마셨다는 메시지를 음료 객체에 전달할 뿐이다.
메시지 송신자는 수신자의 상태 변경에 대해 전혀 알지 못한다는 것을 의미한다. 이것이 캡슐화가 의미하는 것이며 상태를 캡슐 안에 감춰 외부로 노출시키지 않는 것이다.
# 2-5. 식별자
객체는 서로를 구별할 수 있는 특정 프로퍼티가 존재하는데, 이를 식별자라고 한다. 객체 프로퍼티는 어트리뷰트와 객체 두 가지를 가질 수 있다. 어트리뷰트는 값으로 부르기도 한다.
값의 상태는 변화하지 않기 때문에 불변 상태를 가진다고 말한다. 값이 같으면 동일하다고 판단한다. 동일하다고 판단할 수 있는 근거는 값의 상태가 변하지 않기 때문이다.
키라는 어트리뷰트가 자체적인 행동을 가질 수 있는가? 행동을 가지고 키라는 자신의 존재를 변경할 수 있는가? 그렇지 않다. 따라서 값의 상태는 불변 상태가 맞다.
반면, 객체는 행동에 따라 상태가 변한다. 앨리스라는 객체는 행동에 따라 상태가 변한다. 똑 닮은 두 앨리스가 있다고 할때 한명의 앨리스는 케이크를 먹어 키가 작아지고 다른 한명의 앨리스는 버섯을 먹어 키가 커졌다. 이렇듯 행동에 따라 상태가 달라져 별개의 존재가 되기 때문에, 객체간 동등성은 식별자를 통해서만 입증되어야 한다. (이러한 입증은 동일한 시점에 이루어졌다는 것을 가정한다.)
객체의 식별자는 상태 변경에 독립적이어야 한다.
# 2-6. 행동이 상태를 결정한다.
객체를 설계할 때에는 행동 중심으로 이루어져야 한다. 다음은 상태를 행동보다 먼저 정의했을 때 발생하는 문제점들이다.
- 상태를 먼저 결정하는 경우 캡슐화가 저해된다.
- 객체를 협력자가 아닌 고립된 섬으로 만든다.
- 객체 재사용성이 저해된다. 객체 재사용성은 다양한 협력에 참여할 수 있음을 말한다.
협력관계에서 객체 행동은 결국 객체가 완수해야 할 책임을 의미한다. 객체에 대한 책임을 결정하는 과정이 설계를 주도해야 한다는 방법론이 책임-주도 설계이다. (RDD, Responsibility-Driven Design)
# 2-7. 은유와 객체
객체지향이 현실 세계의 단순 모방이라는 것은 틀렸다. 위의 예시에서 살펴봤듯 소프트웨어 세계에서의 음료수병은 스스로의 양을 능동적으로 줄일 수 있어야 한다. 모든 객체는 상태 변화에 있어 수동적이지 않고 능동적이다.
이렇듯 현실 객체보다 소프트웨어 객체가 더 많은 일을 할 수 있는 특징을 의인화(anthropomorphism)라고 한다. 객체지향은 완전히 새로운 세계를 창조하는 것이다.
현실 객체와 소프트웨어 객체의 관계는 은유(metaphor)라는 단어로 표현된다. 객체지향은 은유를 통해 두 세계의 표현적 차이 간극을 줄이는 것이 목표인 것이다. 현실 세계의 음료수를 소프트웨어에서 음료수 객체로 은유적 표현으로 치환하면 사용자 입장에서 그 구조를 쉽게 예측할 수 있게 된다.