"객체지향의 사실과 오해" 의 5장/6장의 읽고 정리한 글입니다.

5. 책임과 메시지
명확한 책임과 역할을 지닌 참가자들이 협력에 참여해야 한다! 적절한 책임이 스스로의 의지와 판단에 따라 각자 맡은 책임을 수행하는 자율적인 객체를 낳고, 자율적인 객체들이 모여 유연하고 단순한 협력을 낳는다. 따라서 협력에 참여하는 객체가 얼마나 자율적인지가 전체 애플리케이션을 품질을 결정한다.
자율적인 객체
객체가 책임을 자율적으로 수행하기 위해서 객체에게 할당되는 책임이 자율적이어야 한다. 책임이 수행할 방법을 제한할 정도로 구체적인 것도 문제이지만, 협력의 의도를 명확하게 표현하지 못할 정도로 추상적인 것 역시 문제이다. 책임은 협력에 참여하는 의도를 명확하게 서명할 수 있는 수준 안에서 추상적이어야 한다.
자율적인 책임의 특징은 객체가 '어떻게 (HOW)' 해야 하는가가 아니라 '무엇 (WHAT)' 을 해야하는가를 설명한다는 것이다. 방법은 객체가 자율적으로 선택할 수 있다.
메시지와 메서드
메시지
하나의 객체는 메시지를 전송함으로써 다른 객체에 접근한다. 메시지를 가리키는 부분을 메시지 이름 (message name) 이라고 하고, 메시지를 전송할 때 추가적인 정보가 필요한 경우 메시지의 인자 (argument) 를 통해 추가 정보를 제공할 수 있다.
메시지는 메시지 이름과 인자의 두 부분으로 구성된다.
메시지이름(인자1, 인자2)
메시지 전송은 수신자와 메시지의 조합으로, 메시지는 메시지 이름과 인자의 조합이므로 메시지 전송은 수신자, 메시지 이름, 인자의 조합이다.
수신자.메시지이름(인자1, 인자2)
(메시지의 외부)
메시지를 수신받은 객체는 자신이 해당 메시지를 처리할 수 있는지 여부를 확인하는데, 메시지를 처리할 수 있다는 것은 객체가 해당 메시지에 해당하는 행동을 수행해야할 책임이 있다는 것을 의미한다. 따라서 객체가 수신할 수 있는 메시지의 모양이 객체가 수행할 책임의 모양을 결정한다.
(메시지의 내부)
그리고 객체의 외부와 내부가 메시지를 기준으로 분리된다. 객체가 제공하는 메시지는 외부의 다른 객체가 볼 수 있는 공개된 영역에 속하고, 메시지를 처리하기 위해 책임을 수행하는 방법은 외부의 다른 객체가 볼수 없는 객체 자신의 사적인 영역에 속한다. 따라서 메시지를 수신받는 객체가 메시지를 변경하지만 않는다면 처리하는 방법을 변경하더라도 메시지를 전송하는 객체가 알 수 없다.
메서드
메시지를 수신받는 객체가 메시지를 처리하기 위해 내부적으로 선택하는 방법을 메서드라고 한다.
- 객체는 메시지를 수신하면 먼저 해당 메시지를 처리할 수 있는지 여부를 확인
- 처리할 수 있다고 판단되면 주어진 책임을 다하기 위해 메시지를 처리할 방법으로 메서드를 선택
메시지는 '어떻게' 수행될 것인지 명시하지 않으며, 단지 '무엇'이 실행되기를 바라는지만 명시하고 어떤 메서드를 선택할 것인지는 전적으로 수신자가 결정한다.
다형성
다형성이란 동일한 메시지에 대해 서로 다른 유형의 객체가 서로 다르게 반응하는 것을 의미한다. 서로 다른 객체들이 다형성을 만족시킨다는 것은 객체들이 동일한 책임을 공유한다는 것을 의미한다. 메시지 송신자의 관점에서 다형적인 수신자들이 메시지를 서로 다른 방식으로 처리하더라도 동일한 책임을 수행하고 있으므로, 수신자는 구분할 필요 없이 자신의 요청을 수행할 책임을 지닌다.
다형성은 메시지 송신자의 관점에서 동일한 역할을 수행하는 다양한 타입의 객체와 협력할 수 있게 한다. 따라서 동일한 역할을 수행할 수 있는 수신자 객체들 사이에 대체 가능성을 가지고, 수신자의 종류를 캡슐화하여 메시지 송신자의 관점에서 메시지를 보낼 때 메시지 수신자의 종류를 알 필요가 없다. 즉, 다형성은 송신자와 수신자 간의 객체 타입에 대한 결합도를 메시지에 대한 결합도로 낮춤으로서 달성된다.
그리고 메시지를 기반으로 한 수신자와 송신자 사이의 낮은 결합도가 설계를 유연하고 확장 가능하며 재사용 가능하게 만든다!

메시지를 따라라
객체지향 애플리케이션은 메시지를 통해 정의된다. 즉 어플리케이션의 객체들의 윤곽을 결정하는 것이 객체들이 주고받는 메시지이며, 따라서 개별적인 객체가 아니라 메시지를 주고 받는 객체들 사이의 커뮤니케이션에 초점을 맞춰야 한다.
독립된 단위의 객체가 아닌 협력이라는 문맥 속에서, 객체를 이용하는 이유는 객체가 다른 객체가 필요로 하는 행위를 제공하기 때문이다. 협력 관계 속에서 다른 객체에게 무엇을 제공해야 하고 다른 객체로부터 무엇을 얻어야 하는가의 관점에서 접근할 때만 훌륭한 책임을 수확할 수 있다. 즉, 객체가 다른 객체에게 제공해야하는 메시지에 대해 고민하라. 객체가 메시지를 선택하는 것이 아니라, 메시지가 객체를 선택하게 해야 한다.
책임-주도 설계
책임-주도 설계는 각 객체가 책임을 수행하는 중에 필요한 정보나 서비스를 제공해줄 협력자를 찾아 해당 협력자에게 책임을 할당하는 순차적인 방식으로 객체들의 협력을 구축한다. 객체가 책임을 완수하기 위해 자신이 보유하고 있지 않은 정보를 필요로 한다면, 필요한 정보를 제공할 책임을 담당하고 있는 다른 객체에게 메시지를 전송해 정보를 요청해야 한다.
책임-주도 설계는 다음과 같은 단계로 이루어진다.
- 애플리케이션이 수행하는 기능을 시스템의 책임으로 보기
- 시스템이 수행할 책임을 구현하기 위해, 협력 관계를 시작할 적절한 객체를 찾아 시스템의 책임을 객체의 책임으로 할당
- 객체가 책임을 완수하기 위해 다른 객체의 도움이 필요하다고 판단되면 어떤 메시지가 필요한지 판단
- 메시지를 수신하기에 적합한 객체를 선택
메시지 수신자는 송신자가 기대한 대로 메시지를 처리할 책임이 있고, 따라서 메시지가 수신자의 책임을 결정한다.
WHAT/WHO 사이클
객체 사이의 협력 관계를 설계하기 위해 먼저 '어떤 행위 (WHAT, 메시지)' 를 수행할 것인지를 결정한 후에 '누가 (WHO, 수신자)' 그 행위를 수행할 것인지를 결정해야 한다. 따라서 필요한 메시지를 먼저 결정한 후에 메시지를 수신하기에 적합한 객체를 선택한다. 그리고 수신된 메시지가 객체의 책임을 결정한다. 즉, 객체가
묻지 말고 시켜라
메시지를 결정하는 시점에서 어떤 객체가 메시지를 수신할 것인지를 알 수 없기 때문에 메시지 송신자는 메시지를 수신할 객체의 내부 상태를 볼 수 없다. 송신자는 수신자가 어떤 객체인지 모르지만 자신이 전송한 메시지를 잘 처리할 것이라고 믿고 메시지를 전송할 수 밖에 없다. '묻지 말고 시켜라' 스타일은 구체적인 타입과 무관하게 메시지를 이해할 수 있는 객체들을 서로 연결하고 협력하도록 만들기 때문에, 캡슐화를 보장하며 결합도를 낮게 유지시키므로 설계를 유연하게 만든다.
객체 인터페이스
인터페이스 (interface) 는 객체가 책임을 수행하기 위해 외부로부터 메시지를 받기 위한 통로이다. 인터페이스를 통해 메시지를 수신하여, 수신한 메시지에 따라 객체가 어떤 책임을 수행하게 된다.
객체의 공용 인터페이스는 객체가 수신할 수 있는 메시지의 목록으로 구성되며, 객체가 협력에 참여하기 위해 수행하는 메시지가 객체의 공용 인터페이스의 모양을 만든다. 먼저 메시지를 결정하고 메시지를 수행할 객체를 나중에 결정하므로 (WHAT/WHO 사이클), 곧 메시지가 수신자의 인터페이스를 결정하게 된다.
그리고 다음의 원칙에 따라 인터페이스를 제공해야한다.
- 추상적인 인터페이스 : 추상적인 수준의 메시지를 수신할 수 있는 인터페이스를 제공한다.
- 최소 인터페이스 : 외부에서 사용할 필요가 없는 인터페이스는 최대한 노출하지 않는다.
- 인터페이스와 구현의 분리
인터페이스와 구현의 분리
구현 (implementation) 은 내부 구조와 작동 방식을 말한다. 소프트웨어는 항상 변하기 때문에 객체의 모든 것을 외부에 공개할 경우 변경 시마다 그 파급 효과가 있으므로, 외부에 노출되는 인터페이스를 앞에 두고 객체의 내부에 숨겨지는 구현을 명확하게 분리해야 한다. (인터페이스와 구현의 분리 원칙) 따라서 송신자와 수신자가 구체적인 구현 부분이 아니라 느슨한 인터페이스에 대해서만 결합되도록 만든다.
객체를 구성하지만 공용 인터페이스에 포함되지 않는 상태, 메서드를 구성하는 코드는 구현에 포함된다.

6. 객체 지도
변경이 잦은 기능을 중심으로 구조를 종속시키는 접근법보다, 안정적인 구조를 중심으로 기능을 종속시키는 접근법이 범용적이고 재사용 가능하며 변경에 유연하게 대처할 수 있는 모델을 만든다. 자주 변경되는 기능이 아니라 안정적인 구조를 따라 역할, 책임, 협력을 구성하라!
기능 대 구조
모든 소프트웨어 제품의 설계에는 기능 (function) 과 구조 (structure) 2가지 측면이 존재한다. 기능 측면의 설계는 제품이 사용자를 위해 무엇을 할 수 있는지에 초점을 맞추고, 구조 측면의 설계는 제품의 형태가 어떠해야 하는지에 초점을 맞춘다. 소프트웨어 요구사항이 예측 불가능하게 변경되므로, 설계자는 사용자가 만족할 수 있는 훌륭한 기능을 제공하는 동시에 예측 불가능한 요구사항 변경에 유연하게 대처할 수 있는 안정적인 구조를 제공해야 한다.
객체지향 접근 방법은 객체의 구조를 기능이 따르게 만들기 위해, 자주 변경되지 않는 안정적인 객체 구조를 바탕으로 시스템 기능을 객체 간의 책임으로 분배한다. 시스템 기능은 더 작은 책임으로 분할되고 적절한 객체에 분배되기 때문에 기능이 변경되더라도 객체 간의 구조는 그대로 유지된다. 이것이 객체를 기반으로 책임과 역할을 식별하고 메시지를 기반으로 객체들의 협력 관계를 구축하는 이유이다!
기능과 구조를 표현하기 위한 기법은 다음과 같다.
- 기능을 수집하고 표현하기 위한 기법 -> 유스케이스 모델링
- 구조를 수집하고 표현하기 위한 기법 -> 도메인 모델링
도메인 모델
도메인은 사용자가 프로그램을 사용하는 대상 분야를 말하며, 모델은 대상을 추상화하고 단순화하여 표현한 것이다. 따라서 도메인 모델은 사용자가 프로그램을 사용하는 대상 영역 내 개념과 개념 간의 관계, 다양한 규칙이나 제약 등을 선택적으로 단순화하고 의식적으로 구조화한 형태이다. 도메인 모델은 단순히 다이어그램이 아니라 설계자, 사용자 등이 바라보는 멘탈 모델 (Mental Model) 이다. 즉, 사람들이 도메인을 이해하고 반응하기 위해 마음 속에 구축한 모델이다.
멘탈 모델은 사용자 모델, 디자인 모델, 시스템 이미지 3가지로 구분된다. 사용자 모델은 사용자가 제품에 대해 마음 속에 가지고 있는 개념들의 모습이고, 디자인 모델은 설계자가 제품에 대해 마음 속에 갖고 있는 시스템에 대한 개념화이다. 시스템 이미지는 최종 제품이다. 설계자는 디자인 모델을 기반으로 만든 시스템 이미지가 사용자 모델을 정확하게 반영하도록 노력해야 한다.

그리고 도메인 모델은 도메인에 대한 사용자 모델, 디자인 모델, 시스템 이미지를 포괄하도록 추상화한 소프트웨어 모델이다. 즉, 도메인 모델은 사용자들이 도메인을 바라보는 관점이며, 설계자가 시스템의 구조를 바라보는 관점인 동시에 소프트웨어 안에 구현된 코드의 모습 그 자체이다. 그리고 객체지향은 도메인에 대한 사용자 모델, 디자인 모델, 시스템 이미지 모두가 유사한 모습을 유지하도록 만드는 것이 가능하며, 이를 연결완전성 혹은 표현적 차이라고 한다.
표현적 차이
소프트웨어 객체가 현실 객체를 왜곡한다고 하더라도 소프트웨어 객체는 현실 객체의 특성을 토대로 구축되는데, 소프트웨어 객체와 현실 객체 사이의 의미적 거리를 가리켜 표현적 차이라고 한다. 그리고 소프트웨어 객체를 창조할 때 현실 객체와 소프트웨어 객체 사이의 차이를 은유를 통해 최대한 줄여야 한다. 여기서 소프트웨어 객체를 창조하기 위해 은유해야 하는 대상은 도메인 모델이다!
도메인 모델을 기반으로 코드를 작성하는 이유는 다음과 같다.
- 사용자가 도메인을 바라보는 관점을 그대로 코드에 반영할 수 있게 한다.
사용자들이 누구보다 도메인의 본질적인 측면을 가장 잘 이해하고 있으므로, 사용자 모델에 포함된 개념과 규칙은 비교적 변경될 확률이 적어 사용자 모델을 기반으로 설계하면 변경에 쉽게 대처할 수 있을 가능성이 높다. - 도메인 모델이 제공하는 구조가 상대적으로 안정적이기 때문이다.
유스케이스
기능적 요구사항이란 시스템이 사용자에게 제공해야 하는 기능의 목록을 정리한 것이다. '사용자'들이 시스템을 통해 달성하고자 하는 목표가 존재하므로, '시스템'이 사용자에게 기능을 제공한다. 따라서 사용자와 시스템 간의 상호작용 관점에서 시스템을 봐라봐야 한다. 그리고 유스케이스는 사용자의 목표를 달성하기 위해 사용자와 시스템 간에 이뤄지는 상호작용의 흐름을 텍스트로 정리한 것이다.
유스케이스의 특성은 다음과 같다.
- 유스케이스는 사용자와 시스템 간의 상호작용을 보여주는 텍스트로, 일련의 이야기 흐름이다.
- 유스케이스는 하나의 시나리오가 아니라 여러 시나리오들의 집합이다.
- 유스케이스는 단순한 기능 목록의 나열이 아니라 이야기를 통해 연관된 기능을 함께 묶을 수 있다.
- 유스케이스는 사용자 인터페이스와 관련된 세부 정보를 포함하지 말아야한다.
- 유스케이스는 내부 설계와 관련된 정보를 포함하지 않는다.
유스케이스는 사용자가 시스템을 통해 무엇을 얻을 수 있고 어떻게 상호작용할 수 있느냐에 관한 정보만 기술된다. 단지 기능적 요구사항을 사용자의 목표라는 문맥을 중심으로 묶기 위한 정리기법일 뿐이다.

기능과 구조 통합
변경에 유연한 소프트웨어를 만들기 위해서는 유스케이스에 정리된 시스템의 기능을 도메인 모델을 기반으로 한 객체들의 책임으로 분배해야 한다. 책임-주도 설계는 유스케이스로부터 첫번째 메시지와 사용자가 달성하려는 목표를, 도메인 모델로부터는 기능을 수용할 수 있는 안정적인 구조를 제공받아 실제로 동작하는 객체들의 협력 공동체를 창조한다.
먼저 객체지향에서는 모든 것이 객체이므로, 시스템을 사용자로부터 전송된 메시지를 수행하기 위해 책임을 수행하는 커다란 객체로 본다. 그리고 유스케이스에서 사용자에게 제공할 기능을 시스템이 수행해야 하는 커다란 규모의 책임으로 바꾸고, 이 책임은 다시 시스템 내 작은 크기 객체들의 협력을 통해 구현될 수 있다. 즉, 객체들 간의 협력에서 가장 첫번째 메시지는 시스템의 기능을 시스템의 책임으로 바꾼 후 얻어진다.
시스템의 기능을 시스템의 책임으로 바꾼 후 기능과 구조를 통합하는 과정은 다음과 같다.
- 시스템에 할당된 커다란 책임은 시스템 내 작은 크기의 객체들이 수행해야 하는 더 작은 규모의 책임으로 세분화된다.
- 여기서 책임을 할당받을 작은 크기의 객체로 도메인 모델에 포함된 개념을 은유하는 객체를 선택해야 한다.
- 협력을 완성하는 데 필요한 메시지를 식별하면서 객체들에게 책임을 할당한다.
- 협력에 참여하는 객체를 구현하기 위해 클래스를 추가하고 속성과 함께 메서드를 구현하면 시스템의 기능이 완성된다.
'Books' 카테고리의 다른 글
| 테스트 주도 개발 정리 - (2) (0) | 2022.01.19 |
|---|---|
| 테스트 주도 개발 정리 - (1) (0) | 2022.01.10 |
| 객체지향의 사실과 오해 정리 - (2) (0) | 2021.12.29 |
| 객체지향의 사실과 오해 정리 - (1) (0) | 2021.12.28 |