본문 바로가기

기술 서적/객체지향의 사실과 오해

[객체지향의 사실과 오해] ch5. 책임과 메시지

우리가 학교에서 CPR 교육을 받을 때, 신고를 부탁하는 사람을 정확히 "지정"하라고 교육한다. 그 이유는 단순히 신고를 해달라는 부탁을 한다면, "누군가 신고하겠지"라는 마음으로 책임감이 분산되어 초기 대응이 불가하기 때문이다.  그렇기에 정확한 한 사람을 지정하여 그 사람에게 신고에 대한 책임을 부여하는 것을 장려한다.

 

객체의 세계도 마찬가지다. 훌륭한 객체지향의 세계는 명확하게 정의된 역할과 책임을 지닌 객체들이 상호 협력하는 세계이다.


자율적인 책임

 

>설계의 품질을 좌우하는 책임

책임 : 요청을 처리하기 위해 객체가 수행하는 행동

자율적인 객체 : 자신이 맡은 책임을 스스로의 판단에 따라 수행

 

ex) 앨리스의 일화 : 증언에 대한 what과 how의 요구

두가지 같은 요청이 있다.

1) 증언하라
2) 목격했던 장면을 떠올려라 > 떠오르는 기억을 재구성하라 > 말로 간결하게 표현하라

 

2번은 모자장수가 증언하기 위해 선택가능한 자유의 범위를 지나치게 제한한다. 따라서 모자장수는 책임 수행을 위해 왕의 명령에 의존할 수 밖에 없게 된다.

 

> 너무 추상적인 책임

그럼 [증언하라]라는 요구를 [설명하라]로 바꾸면 어떻게 될까? 

 

- 장점: 유연성이 증가한다

- 단점 : 추상화되어 의도와 책임이 생략될 수 있다

 

> '어떻게' 가 아니라 '무엇을'

 

자율적인 책임의 특징은 객체가 어떻게 해야하는가가 아니라 무엇을 해야 하는가를 설명하는 것이다. 

 

예를 들어 다음 메서드 시그니처가 있다고 가정해보자

 

1) public int getSumByUsingPlus(int ...a)

2) public int getSum(int ...a)

 

1번은 수들의 합을 +라는 연산을 통해 가져오라고 말한다. 즉, 연산에 대한 방법(how)가 담겨진 메세지이다.

2번은 그저 수를 가져오라는 (what)이 담겨진 메세지이다.

 

1번 메서드는 내부 구현 방식이 외부에 투영되어 있다. +를 사용하지 않는 연산은 이 메세지를 사용할 수 없다. 즉 객체의 자율성이 매우 제한되는 것이다. 이에 비해 2번 메서드는 내부 구현방식과 관련없이 역할을 투영하고 있다. 객체는 자유롭게 합을 구해 반환해주면 된다.

 

이처럼 메시지는 "어떻게"가 아닌 "무엇을"을 담아야 한다. what을 처리하는 방식을 전적으로 수신자인 객체의 자유이다.

 


메시지와 메서드 

 

- 하나의 객체는 유일하게 메세지를 전송함으로써 다른 객체에게 접근한다.

- 메시지 전송은 수신자. 메시지이름(인자)의 조합이다.

- 메시지를 수신할 수 있다는 것 == 객체가 메시지에 해당하는 책임을 수행가능하다는 것

- 송신자 : 외부에서 메시지에 대해서만 볼 수 있다.

- 수신자 : 내부에서 메시지 처리 방식을 선택할 수 잇다.

 

예를 들어 왕이 모자장수에게 어제, 왕국에서 목격한 것을 요청한다면 메시지는 다음의 형태일 것이다.

모자장수.증언하라(어제, 왕국)
= 수신자.메시지이름(인자1, 인자2)

 

 

>메서드

- 메서드 : 메시지 처리를 위해 내부적으로 선택하는 방법

- 송신자는 메시지에 "무엇을 처리해달라"를 담는다.

- 수신자는 "무엇을 처리하는 방법"을 내부에서 자율적으로 선택할 수 있다.

 

>다형성

- 다형성 : 서로 다른 유형의 객체가 동일한 메세지에 서로 다르게 반응하는 것

- 다형성을 만족한다 == 객체들이 동일한 책임을 공유한다

- 송신자의 관점1 : 메세지 수신자들은 같은 책임을 수행할 수 있다 

- 송신자의 관점2 : 수신자 종류를 몰라도 메시지를 전송가능 > 수신자의 종류를 캡슐화

- 송신자-수신자의 객체 타입의 결합도 >> 메시지에 대한 결합도로 완화


> 메시지의 결합도 완화 => 유연하고 확장 가능하고 재사용성이 높은 협력

- 1) 협력이 유연해짐 : 송신자는 수신자가 메시지 수신이 가능하다면 누구인지 상관x(타입을 몰라도 됨)

- 2) 협력 확장 가능 : 메시지만으로 느슨한 관계

- 3) 협력 재사용 : 다양한 객체가 수신자의 자리를 대체 가능

 


메시지를 따라라

 

객체지향의 강력함은 클래스가 아니라 메시지에서 나온다. 클래스를 중점으로 두는 설계는 유연하지 못하고 확장하기 어렵다. 객체지향 패러다임으로의 전환은 시스템을 정적인 클래스의 집합이 아니라 동적인 객체의 집합으로 바라본다

객체지향 애플리케이션은
클래스를 통해 만들어지지만
메시지를 통해 정의된다
-[Metz 2012]-

 

객체 자체에 초점을 맞출 경우

- 협력이라는 문맥을 배제한 채 내부의 데이터 구조를 생각한 후 데이터 조작에 필요한 오퍼레이션을 나중에 고려한다

- 객체의 내부 구조를 개체 정의의 일부로 만들어 자율성을 침해한다. ex) getSumByPlus

 

따라서, 객체를 협력이라는 문맥 안에서 생각해야 한다. 객체를 이용하는 이유는 객체가 다른 객체가 필요로 하는 행위를 제공하기 때문이다. 훌륭한 객체지향 설계는 어떤 객체가 메시지를 전송할 수 있는가와 어떤 객체가 어떤 메시지를 이용할 수 있는가를 중심으로 협력관계를 구축하는 것이다. 따라서 객체가 메시지를 선택하게 하는 것이 아니라 메시지가 객체를 선택하게 해야 한다.


>책임 주도 설계 다시보기

 

1) 애플리케이션 기능을 시스템의 책임으로 바라본다

2) 적절한 객체를 찾아 시스템 책임을 객체의 책임으로 할당한다

3) 도움이 필요하면 어떤 메시지를 전송할 것인지 결정한다

4) 메세지 수신에 적합한 객체를 선택한다


>what/who 사이클

 

What/Who 사이클 : 어떤 행위(what-메시지)을 수행할 것인지 결정한 후에 누가(who) 그 행위를 수행할 것인지 결정

 

예를 들어 getSum() 메서드가 있다면

기존: calculator 객체에 getSum()이라는 책임을 지어야 겠구나!

what/who 사이클 : getSum()이라는 메시지를 누가 수행하게 할까? => calculator가 적당하겠다!

로 생각해야 한다.

 


>묻지 말고 시켜라

 

데메테르 법칙 : 송신자가 수신자를 모르는 상태로 그저 메시지를 잘 처리할 것이라는 것을 믿고 전송

- 객체는 다른 객체의 상태를 묻지 말아야 한다.

 


객체 인터페이스

 

>인터페이스

인터페이스 : 두 사물의 경계 지점에서 상호작용할 수 있게 이어주는 방법

- 1) 사용법을 익히면 동작방식을 몰라도 조작가능
ex) 자동차 원리를 몰라도 운전할 수 있다. 자동차는 내부의 복잡함을 감추고 운전에 필요한 최소 요소만을 운전자에게 노출시킨다.

- 2) 내부 동작방식이 변해도 외부 사용자에게 영향을 끼치지 않음
ex) 가솔린 차를 운전하다 전기차로 바꾸어도 영향이 없다.

- 3) 대상이 변경되어도 동일한 인터페이스를 제공하면 상호작용이 가능
ex) 하나의 자동차를 운전가능하면 다른 차도 운전가능하다.

 

 

>공용 인터페이스

- 공용 인터페이스 : 외부에 공개된 인터페이스

- 인터페이스는 오직 메시지 전송을 통해서 접근가능하다

- 자기 자신과의 상호작용 또한 메시지 전송을 통해서만 가능하다

- 공용 인터페이스는 객체가 수신할 수 있는 메시지의 목록이다.

 

> 책임, 메시지 그리고 인터페이스

 

- 협력에 참여하는 객체는 자율적이어야 한다

- 자율성이란 스스로 책임을 수행하는 방법을 결정가능한 것을 의미한다

- 송신자는 메시지를 전달하며 그 수신 객체 타입을 몰라도 된다.

- 객체가 메시지를 수신하면 적절한 책임이 수행된다

- 메시지 수신자는 내부에서 자율적인 메서드 선택이 가능하며 이 부분이 다형성으로 이어진다

- 메시지와 메서드는 객체의 외/내부를 분리한다

- 객체의 인터페이스는 수신가능한 메시지의 목록(책임)으로 채워진다

 


인터페이스와 구현의 분리

 

>객체 관점에서 생각하는 방법

[ 객체지향적인 사고방식 이해를 위한 3가지 원칙]

1원칙:  좀 더 추상적인 인터페이스
ex) getSumbyPlus > getSum

2원칙: 최소 인터페이스
ex) 외부에서 사용할 필요가 없는 인터페이스는 노출x

3원칙: 인터페이스와 구현 간의 차이가 있다는 점을 인식
- 인터페이스 : 객체간 결합도를 결정짓는 공개된 경계점
- 객체 내부 : 객체가 가지는 상태와 메서드 구현
- 훌륭한 객체는 구현을 몰라도 인터페이스만 알면 쉽게 상호작용이 가능
- 만약, 객체 구현이 외부에 공개되어 있다면 작은 부분을 수정하더라도 변경에 의한 파급효과는 객체 공동체 구석구석까지 파고들 것이다
- 따라서, 객체간의 구현이 아닌 느슨한 인터페이스에 한해 결합되도록 하는 것이 중요하다

 

> 캡슐화

캡슐화1 :  데이터 캡슐화(상태와 행위의 캡슐화)
- 멤버변수/함수를 하나의 단위로 묶는다

캡슐화2. 사적인 비밀의 캡슐화
- 변경이 빈번한 구현은 객체 안으로 숨길 수 있다
- 최소한의 범위/ 필요한 부분에 한해 공개한다

[정리] 책임의 자율성이 품질을 결정한다

 

1. 협력을 단순하게 만든다

: 책임이 적절히 추상화된다

 

2. 외/내부를 명확히 분리한다

: 캡슐화를 통해 인터페이스와 구현이 분리된다

 

3. 내부방법을 변경해도 외부에 영향이 없다

: 책임이 자율적일 수록 변경에 의해 수정되어야 하는 범위가 좁고 명확해진다

: 변경의 파급효과가 객체 내부로 캡슐화되기에 결합도가 낮아진다

 

4. 협력의 대상을 다양하게 선택할 수 있는 유연성을 제공한다

: 송신자는 객체타입과 무관하게 전송이 가능하다

: 수신자는 메시지를 수신할 수만 있다면 누구든지 대체가능하다

 

5. 객체의 역할 이해도 강화

- 객체의 존재 이유를 명확하게 표현할 수 있다

- 동일목적 달성을 위해 강하게 연관된 책임으로 구성되기에 응집도가 높아진다