1. Introduction
객체지향을 처음 접하게 될 때 가장 많이 듣는 설명은 “객체지향이란 실세계를 직관적으로 모델링할 수 있는 패러다임”이다.
이러한 설명은 객체지향의 철학을 설명하는데는 적합하지만 실용적인 관점에서의 객체지향 설계를 설명하기에는 적합하지 않다.
객체지향의 목표는 실세계를 모방하는 것이 아니다. 애플리케이션을 개발하면서 객체에 직접적으로 대응되는 실세계의 사물을 발견할 확률은 매우 낮다. 가령, 스스로 돈을 충전하고 인출할수 있는 “계좌” 객체가 있다고 해보자. 이것을 실세계의 계좌에 대입한다면 의미가 맞지 않을 것이다.
“소프트웨어 시스템이 해결하려고 하는 실재는 잘해봐야 먼 친척밖에는 되지 않는다 - Bertrand Meyer 2000”
하지만 객체지향은 실세계의 모방이라는 개념이 반복적으로 널리 사용되는 이유는 뭘까? 그것은 실세계에 대한 비유가 객체지향의 다양한 측면을 이해하는데 매우 효과적이기 때문이다.
- 객체를 스스로 생각하고 결정하는 현실 세계의 생명체와 비유
- 상태와 행위를 캡슐화(encapsulation)하는 소프트웨어 객체의 자율성(autonomous)을 설명하는데 효과적
- 현실 세계의 사람들이 암묵적인 약속과 명시적인 계약을 기반으로 협력하며 목표를 달성해나가는 것
- 객체들이 서로 Message를 주고받으며 공동의 목표를 향해 협력(Collaboration)
다음 예제를 통해 객체지향의 세계를 조금 더 자세히 들여다보자.
2. 협력하는 사람들
출근길에 매일 들리는 커피숍을 떠올려보자.
손님은 캐시어에게 “아아 한잔 주세요” 라는 메세지와 함께 커피 주문을 한다.
주문을 받은 캐시어는 미리 준비된 컵의 옆면에 “아아”라는 글자를 적은 후 테이블에 올려놓는다.
바리스타는 바쁜 손놀림으로 줄지어 기다리고 있는 컵을 들어 주문 내역을 살피고 커피를 제조한다.
바리스타가 제조가 끝나면 주문대 우측 테이블에 올려 놓는다.
캐시어는 “xxx고객님~ 주문하신 아아 나왔습니다.” 라는 말과 함께 손님에게 커피가 나왔음을 알린다.
끝으로 손님은 재빠르게 커피를 들고 회사로 향한다.
이렇게 커피 한잔을 주문하는 작은 이벤트에도 여러 역할을 가진 사람들이 책임을 다하며 협력한다.
2.1 객체지향의 가장 중요한 개념 3가지: 역할, 책임, 협력
- 역할
- 산뜻한 커피를 매일 아침 즐길 수 있는 이유는 주문을 하는 손님, 주문을 받는 캐시어, 커피를 만드는 바리스타라는 역할이 있기 때문이다.
- 책임
- 손님, 캐시어, 바리스타는 각각의 주문 과정에서 자신의 책임을 다한다.
- 손님은 주문하는 책임, 캐시어는 주문을 받을 책임, 그리고 바리스타는 커피를 제조할 책임을 가진다.
- 협력
- 서로 다른 역할과 책임을 가진 “객체”들이 열심히 협력하며 “커피 주문과 전달”이라는 공동의 목표를 이뤄낸다.
이렇게 커피 주문이라는 협력에 참여하는 모든 사람들은 커피가 정확하고 주문될 수 있도록 맡은 바 역할과 책임을 다하는 것이다.
2.2 Request와 Response으로 구성된 협력
세상에서 발생하는 대부분의 문제는 혼자의 힘으로는 해결하기 힘든 경우가 많다. 앞서 살펴본 커피 주문이라는 문제도, 문제 해결에 필요한 지식을 알고 있거나, 서비스를 제공해줄 수 있는 사람에게 도움을 요청 (request)한다.
Request는 연쇄적으로 발생한다
일반적으로, 하나의 문제를 해결하기 위해 다수의 사람 혹은 역할이 필요하기 떄문에, 한 사람에 대한 요청이 또 다른 사람에 대한 요청을 유발하는 경우가 일반적이다 (손님 -> 캐시어 -> 바리스타)
요청을 받은 사람은 자신의 책임을 다하면서 필요한 지식이나 서비스를 제공한다. 즉, 요청에 응답 (response)한다.
이렇게 객체들은 주어진 역할과 책임을 다하며, request와 response을 통해 다른 객체들과 협력하며 더 거대하고 복잡한 문제를 해결한다.
2.3 역할 (Role) vs 책임 (Responsibility)
사람들은 다른 사람과 협력하는 과정에서 역할 (role)을 부여받는다.
- 손님, 캐시어, 바리스타 역할
특정 역할을 맡은 사람은 그 역할에 부여된 적절한 책임을 다한다.
- 손님: 주문을 할 책임
- 캐시어: 주문을 받을 책임
- 바리스타: 커피를 만들 책임
역할은 책임을 내포한다
역할은 어떤 협력에 참여하는 사람이 협력 안에서 차지하는 책임을 의미한다. 만약 누군가 캐시어라는 역할(role)을 맡았다면, 손님으로 부터 주문을 성실을 받을 책임(responsibility)을 받아들이는 것이다.
역할과 책임에 관한 중요한 사실 4가지
- 여러 객체가 동일한 역할을 수행할 수 있다.
- 손님 입장에서는, 커피만 마실수 있으면 어떤 캐시어가 주문을 받던 상관없다.
- 캐시어 입장에선, 커피만 받을수 있으면 어떤 바리스타가 제조를 하던 상관없다.
- 역할에 따르는 동일한 책임을 완수할 수 있다면, 누가 특정 역할을 맡던 상관없는 것이다.
- 역할은 “대체 가능성”을 의미한다.
- 손님 입장에서 캐시어는 대체 가능(substitutable)하다.
- 즉, 두 명이 동일한 역할을 수행할 수 있다면, 요청자 입장에서 누가 수행하던 문제가 되지 않는다.
- 한 객체가 동시에 여러 역할을 수행할 수 있다.
- 어떤 사람이 캐시어와 바리스타라는 두개의 역할을 동시에 수행할수도 있다.
- 책임을 수행하는 방법은 자율적으로 선택할수 있다.
- Request를 받은 사람들은 그것을 처리할 방법을 자유롭게 선택할 수 있다.
- 바리스타가 자신만의 노하우로 커피를 제조할수도 있고, 어떤 바리스타는 카푸치노 커품을 이용해 데코레이션을 할수도 있다.
- 중요한것은, 동일한 요청을 받더라도 역할을 수행하는 사람마다 서로 다른 방식으로 요청을 처리할수 있다. 이러한 능력을 다형성 (polymorphism)이라고 한다.
3. 역할, 책임, 협력
앞서 살펴본 예제에서
- 사람 -> 객체 (Object)
- 에이전트의 요청 -> 메시지 (Message)
- 에이전트가 요청을 처리하는 방법 -> 메서드 (Method)
다음과 같이 치환하면 대부분의 설명을 OOP 문맥으로 옮겨올 수 있다.
사람들이 공통의 목표를 달성하기 위해 협력한다면, 객체들은 애플리케이션의 구현을 위해 협력한다.
애플리케이션의 기능은 더 작은 책임을 분할되고, 책임은 적절한 역할을 수행할 수 있는 객체에 의해 수행된다.
결국, 시스템은 역할과 책임을 수행하는 객체로 분할되고, 시스템의 기능은 객체 간의 연쇄적인 요청/응답의 흐름으로 구성된 협력으로 구현된다.
객체지향 설계라는 예술은 “적절한 객체에게 적절한 책임을 할당하는 것“이다.
얼마나 적절한 책임을 선택하느냐가 애플리케이션의 아름다움을 결정한다.
4. 협력 속에 사는 객체
객체지향 애플리케이션의 윤곽을 결정하는것은 책임, 역할, 협력이지만, 실제로 협력에 참여하는 친구는 객체 (object)이다.
객체는 애플리케이션의 기능을 구현하기 위해 존재한다. 하지만 아주 작은 기능이라도 하나의 객체가 모두 처리하기는 버거우므로 보통 객체는 다른 객체와의 협력을 통해 기능을 구현한다.
객체지향 애플리케이션의 아름다움을 결정하는것이 “협력”이라면, 협력의 품질을 결정하는것은 객체의 품질이다.
객체의 두가지 덕목
- 객체는 충분히 협력적이어야 한다.
- 객체는 다른 객체의 요청에 귀 기울여야하고, 다른 객체에게 적극적으로 도움을 요청할수 있어야한다.
- 외부의 도움을 무시한채, 모든 것을 스스로 처리하다가는 내부 복잡도에 의해 자멸하고 만다.
- 충분히 협력적이라는 말이 다른 객체의 명령에 따라 복종하는 것이 아닌, 요청에 응답할 뿐이다. 어떤 방식으로 응답할지는 스스로 판단한다.
- 객체는 충분히 자율적이어야 한다.
- 객체는 맡은 책임을 수행할 방법을 스스로의 결정과 판단에 따라 결정할수 있다.
객체지향 설계의 묘미는, 다른 객체와 협력하기 위해 충분히 개방적인 동시에 협력에 참여하는 방법을 스스로 결정할수 있을만큼 충분히 자율적이어야 한다는것이다.
State와 Behavior을 함께 지닌 객체
흔히 객체를 state(상태)와 behavior(행동)을 함께 지닌 실체라고 정의한다.
- 이 말은, 객체가 협력에 참여하기 위해서 어떤 행동을 할때 그것에 필요한 상태도 함께 지니고 있어야 한다는것을 의미한다.
- 바리스타가 커피 제조법(state)을 모른다면, 커피제조를 할 수 없을것이다.
객체의 자율성은 객체의 내부와 외부를 명확하게 구분하는 것으로부터 나온다.
객체의 사적인 부분은 스스로 관리하고 접근을 허락해서는 안된다. 외부에서 접근이 허락된 수단을 통해서만 객체와 소통해야한다. (private & public interface)
객체는 다른 객체가 무엇(what)을 수행하는지만 알수있고, 어떻게(how) 하는지는 알 수 없다.
객체는 행동을 위해 필요한 state를 포함하는 동시에, 특정한 behavior을 수행하는 방법을 스스로 결정할 수 있어야 한다.
따라서, 객체는 state와 behavior을 묶는 자율적인 존재이다.
협력과 메시지
실제 세상에서 사람들은 서로 협력하기 위해 문자, 전화, 편지 등 다양한 방법으로 의사소통을 한다.
하지만 객체지향 세계에서 객체는 오직 message (메시지)만을 통해서만 소통한다.
- 한 객체에서 다른 객체에게 요청 하는것을 메시지를 전송
- 한 객체에서 다른 객체로부터 요청을 받은것을 메시지를 수신
따라서, 객체지향 세계에서 협력은 메시지를 전송하는 객체와 메시지를 수신하는 객체의 관계로 구성된다.
- 메시지를 전송하는 객체를 sender (송신자)
- 메시지를 수신하는 객체를 receiver (수신자)
메서드와 자율성
메시지의 수신자는 먼저 수신된 message를 이해할 수 있는지를 판단한 후, 미리 정해진 자신만의 방법에 따라 메시지를 처리한다.
- 이처럼 객체가 수신된 message를 처리하는 방법을 method라고 한다.
OOP에서 method는 class안에 포함된 function 또는 procedure로 구현된다.
- 따라서, 어떤 객체에게 message를 전송하면, message에 대응하는 특정 method가 실행된다.
- 즉, message에 맞게 runtime에 method를 선택할 수 있는것이다. 이것이 OOP 언어와 다른 언어를 구분짓는 핵심 특징중 하나다.
Message와 Method를 분리는, 객체의 자율성을 증진시킨다
바리스타에게 “커피를 제조해달라”라는 message가 들어오면, 바리스타는 커피머신으로 제조할수도 있고 수작업으로 제조할수도 있다 (자율적으로 method 선택).
외부의 요청이 무엇인지를 표현하는 message와, 요청을 처리하기 위한 구체적인 방법인 method를 분리하는것은 객체의 자율성을 높이는 핵심 메커니즘이다.
- 이것은 encapsulation (캡슐화)하고도 밀접한 관련이 있다.