나를 기록하다
article thumbnail
반응형

객체지향의 사실과 오해

훌륭한 객체지향 설계란 겉모습은 아름답지만 협력자들을 무시하는 오만한 객체를 창조하는 것이 아니라 조화를 이루며 적극적으로 상호작용하는 협력적인 객체를 창조하는 것이다. 비록 그 객체를 따로 뗴어놓고 봤을 때는 겉모습이 다소 기묘하고 비합리적이더라도 말이다. 객체의 모양을 빚는 것은 객체가 참여하는 협력이다.

 

협력

누가 파이를 훔쳤지?

앨리스가 재판장에 도착했을 때 하트 왕과 하트 여왕은 옥좌에 앉아 있었다. 그리고 주위에는 온갖 종류의 새와 짐승과 한 벌의 카드가 모여 있었으며 그들 앞에 파이를 훔쳤다는 혐의를 받고 있는 하트 잭이 사슬에 묶인 채 병사들의 감시를 받으며 서 있었다. 하트 잭의 옆에는 하얀 토끼가 한 손에는 트럼펫을, 다른 한 손에는 양피지 두루마리를 들고 서 있었다. 법정의 한 가운데에 위치한 탁자 위에는 파이가 수북이  쌓인 접시가 놓여 있었다.

재판 장면이 신기했던 앨리스는 법정 안에 있는 것들의 이름을 맞추면서 기뻐했다.

"저 분이 판사야. 저 커다란 가발을 보면 알 수 있어."

앨리스는 중얼거렸다. 자세히 살펴보니 그 판사는 다름 아닌 왕이었다.

"첫 번째 목격자를 불러라."

왕이 명령하자 하얀 토끼는 트럼펫을 세 번 불고 큰 소리로 목격자를 불렀다.

첫 번째 목격자는 모자 장수였다. 그는 한 손에는 찻잔을, 다른 손에는 버터 바른 빵 한 조각을 들고 입장했다.

"증언하라."

왕이 명령하자 모자 장수는 벌벌 떨며 자기 자신에 대해 설명하기 시작했다. 모자 장수는 재판과 상관이 없는 이야기만 중언부언 떠들었다. 불쌍한 모자 장수는 찻잔과 빵을 떨어뜨리고 한쪽 무릎을 꿇었다.

"저는 보잘것없는 사람입니다. 폐하."

"너는 정말 보잘것없는 증인이로구나."

왕이 말했다.

"네가 아는 게 그것뿐이라면 이제 증인석에서 내려와 가도 좋다."

왕이 허락하자 모자 장수는 신발을 신을 틈도 없이 서둘러 법정을 빠져나갔다.

객체지향의 세계는 동일한 목적을 달성하기 위해 협력하는 객체들의 공동체이다.

 

재판 속의 협력

요청과 응답
  • 누군가가 왕에게 재판을 요청함으로써 재판이 시작
  • 왕이 하얀 토끼에게 증인을 부를 것을 요청
  • 왕의 요청을 받은 토끼는 모자 장수에게 증인석으로 입장할 것을 요청
  • 모자 장수는 증인석에 입장함으로써 토끼의 요청에 응답
  • 모자 장수의 입장은 왕이 토끼에게 요청했던 증인 호출에 대한 응답
  • 이제 왕은 모자 장수에게 증언할 것을 요청
  • 모자 장수는 자신이 알고 있는 내용을 증언함으로써 왕의 요청에 응답

 

왕과 하얀 토끼, 모자 장수 사이의 협력 관계

왕과 하얀 토끼, 모자 장수 사이의 협력 관계


책임

객체지향의 세계에서는 어떤 객체가 어떤 요청에 대해 대답해 줄 수 있거나, 적절한 행동을  할 의무가 있는 경우 해당 객체가 책임을 가진다고 말한다.

[예시] 왕은 재판을 수행하라는 요청에 응답해야 하므로 재판을 수행할 책임을 진다.

 

책임의 분류

객체의 책임 2가지

  1. 무엇을 알고 있는가(knowing)
  2. 무엇을 할 수 있는가(doing)
하는 것(doing)
  • 객체를 생성하거나 개선을 하는 등의 스스로 하는 것
  • 다른 객체의 행동을 시작시키는 것
  • 다른 객체의 활동을 제어하고 조절하는 것
아는 것(knowing)
  • 개인적인 정보에 관해 아는 것
  • 관련된 객체에 관해 아는 것
  • 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것

 

책임이란?
  • 객체의 외부에 제공해 줄 수 있는 정보(아는 것의 측면)와 외부에 제공해 줄 수 있는 서비스(하는 것의 측면)의 목록
  • 객체의 공용 인터페이스(public interface)를 구성

 

책임과 메시지

  • 메시지 전송(message-send): 객체가 다른 객체에게 주어진 책임을 수행하도록 요청을 보내는 것
  • 송신자: 협력을 요청하는 객체
  • 수신자: 요청을 처리하는 객체

설계 초반에는 어떤 객체가 어떤 책임을 가지고 어떤 방식으로 서로 협력해야 하는지에 대한 개요를 아는 것만으로 충분하다.

책임과 협력의 구조가 자리를 잡기 전까지는 책임을 구현하는 방법에 대한 고민은 미뤄둔다.

 

객체지향 설계는 협력에 참여하기 위해 어떤 객체가 어떤 책임을 수행해야 하고 어떤 객체로부터 메시지를 수신할 것인지를 결정하는 것으로부터 시작된다. 어떤 클래스가 필요하고 어떤 메서드를 포함해야 하는지를 결정하는 것은 책임과 메시지에 대한 대략적인 윤곽을 잡은 후에 시작해도 늦지 않다.


역할

앨리스 이야기에서 왕은 판사, 모자 장수는 증인이라는 역할을 수행
  • 역할을 사용하는 이유는 역할이 재사용 가능하고 유연한 객체지향 설계를 낳는 매우 중요한 구성요소이기 때문

 

판사와 증인

역할은 대체 가능하다 (왕 → 여왕, 모자 장수 → 앨리스)
  • 누군가가 '여왕'에게 재판을 요청함으로써 재판이 시작
  • '여왕'이 하얀 토끼에게 증인을 부를 것을 요청
  • '여왕'의 요청을 받은 토끼는 '앨리스'에게 증인석으로 입장할 것을 요청
  • '앨리스'는 증인석에 입장함으로써 토끼의 요청에 응답
  • '앨리스'의 입장은 '여왕'이 토끼에게 요청했던 증인 호출에 대한 응답
  • 이제 '여왕'은 '앨리스'에게 증언할 것을 요청
  • '앨리스'는 자신이 알고 있는 내용을 증언함으로써 '여왕'의 요청에 응답

 

역할을 사용하여 추상화
  • 누군가가 '판사'에게 재판을 요청함으로써 재판이 시작
  • '판사'이 하얀 토끼에게 증인을 부를 것을 요청
  • '판사'의 요청을 받은 토끼는 '증인'에게 증인석으로 입장할 것을 요청
  • '증인'는 증인석에 입장함으로써 토끼의 요청에 응답
  • '증인'의 입장은 '역할'이 토끼에게 요청했던 증인 호출에 대한 응답
  • 이제 '판사'은 '증인'에게 증언할 것을 요청
  • '증인'는 자신이 알고 있는 내용을 증언함으로써 '판사'의 요청에 응답

역할을 사용하여 추상화

역할을 대체할 수 있는 객체는 동일한 메시지를 이해할 수 있는 객체로 한정된다.

동일한 역할을 수행하는 객체들이 동일한 메시지를 수신할 수 있기 때문에 동일한 책임을 수행할 수 있다.

역할 → 유사한 협력 추상화 → 인지 과부하 감소
다양한 객체들이 협력에 참여 → 협력이 유연, 재사용성 높아짐
역할은 객체지향 설계의 단순성(simplicity), 유연성(flexibility), 재사용성(reusability)을 뒷받침하는 핵심 개념

 

역할의 가치

협력 추상화
  • "왕-하얀 토끼-모자 장수"의 협력, "여왕-하얀 토끼-앨리스"의 협력 → "판사-하얀 토끼-증인" 하나의 추상적인 협력으로 대체
  • 대체 가능성
대체 가능성
  • 객체가 역할을 대체 가능하기 위해서는 협력 안에서 역할이 수행하는 모든 책임을 동일하게 수행할 수 있어야 한다.
  • 객체가 역할에 주어진 책임 이외에 다른 책임을 수행할 수도 있다.
    • 모자 장수는 증인으로서의 역할 + 모자를 판매하는 모자 장수로서의 본질적인 책임
    • 앨리스는 잠시만 증인의 역할을 할 뿐 이야기 내내 주인공으로서의 역할
  • 객체는 역할이 암시하는 책임보다 더 많은 책임을 가질 수 있다.(일반화/특수화 관계)

역할의 대체 가능성은 행위 호환성을 의미하고, 행위 호환성은 동일한 책임의 수행을 의미한다.


객체의 모양을 결정하는 협력

객체지향에 대한 첫 번째 선입견 - 데이터를 저장하기 위해 객체가 중요한다.
  • 데이터는 단지 객체가 행위를 수행하는 데 필요한 재료
  • 객체가 존재하는 이유는 행위를 수행하며 협력에 참여하기 위해서다.
  • 따라서 실제로 중요한 것은 객체의 행동, 즉 책임이다.
객체지향에 대한 두 번째 선입견 - 객체지향이 클래스와 클래스 간의 관계를 표현하는 시스템의 정적인 측면에 중점을 둔다.
  • 중요한 것은 정적인 클래스가 아닌 협력에 참여하는 동적인 객체
  • 클래스는 단지 시스템에 필요한 객체를 표현하고 생성하기 위해  프로그래밍 언어가 제공하는 구현 메커니즘
  • 객체가 협력 안에서 어떤 책임과 역할을 수행할 것인지를 결정하는 것이 중요

 

협력을 따라 흐르는 객체의 책임

올바른 객체를 설계하기 위한 필요 조건
  • 먼저 견고하고 깔끔한 협력을 설계해야 한다.
  • 협력을 설계한다는 것은 설계에 참여하는 객체들이 주고받을 요청과 응답의 흐름을 결정한다는 것을 의미
  • 이렇게 결정된 요청과 응답의 흐름은 객체가 협력에 참여하기 위해 수행될 책임이 된다.
  • 객체에 책임을 할당하고 나면 책임은 객체가 외부에 제공하게 될 행동이 된다.
  • 즉, 행동을 결정한 후, 그 행동을 수행하는 데 필요한 데이터를 고민해야 한다.
  • 그리고 객체가 협력에 참여하기 위해 필요한 데이터와 행동이 어느 정도 결정된 후에 클래스의 구현 방법을 결정해야 한다.
협력이라는 문맥안에서 객체
  • 객체지향 - 올바른 객체에 올바른 책임을 할당하는 것과 관련된 모든 것
  • 협력이라는 문맥 안에서 객체를 생각하는 것 - 올바른 객체지향 어플리케이션을 구현하는 것
객체지향 시스템에서 가장 중요한 점: 충분히 자율적인 동시에 충분히 협력적인 객체를 창조하는 것
방법: 객체를 충분히 협력적으로 만든 후 협력이라는 문맥 안에서 객체를 충분히 자율적으로 만들기

 

책임-주도 설계

정의
  • 객체의 책임을 중심으로 시스템을 구축하는 설계 방법
설계 절차
  • 시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악
  • 시스템 책임을 더 작은 책임으로 분할
  • 분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당
  • 객체가 책임을 수행하는 중에 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾음
  • 해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력

 

디자인 패턴

역할
  • 책임-주도 설계의 결과를 표현

예시 - COMPOSITE 패턴

COMPOSITE 패턴의 역할
  • 전체와 부분을 하나의 단위로 추상화해야 하는 경우에 사용할 수 있는 패턴
  • 클라이언트 입장에서 메시지 수신자가 부분(파일)인지 전체(폴더)인지에 상관 없이 동일한 메시지(경로 변경)를 이용해 동일한 방식으로 대상과 상호작용하고 싶을 때 사용할 수 있는 패턴

GoF의 디자인 패턴에 수록돼 있는 COMPOSITE 구조

  • 중요한 점: 그림에 표현돼 있는 구성 요소가 클래스와 메서드가 아닌 '협력'에 참여하는 '역할'과 '책임'이라는 사실
  • 부분과 전체가 투명하고 동일한 인터페이스를 제공해야 한다는 제약하에서 식별된 역할, 책임, 협력을 제공하는  한 가지 설계의 예제
부분별 역할
  • Component
    • 클라이언트와 협력할 수 있는 공용 인터페이스를 정의
    • 다른 Component를 추가하거나(Add()), 제거하거나(Remove()), 포함된 Component를 반환하는(GetChild())하는 책임
  • Leaf
    • 공용 인터페이스에 대한 오퍼레이션 호출에 응답할 수 있는 기본적인 행위
  • Composite
    • 외부로부터 부분에 대한 세부 사항을 감추고 포함된 부분을 하나의 단위로 행동하는 역할
  • Client
    • Component에게 메시지를 요청함으로써 협력하는 임의의 역할

 

하나의 클래스가 세 가지 역할을 모두 수행하는 COMPOSITE 패턴의 예시

Composite, Leaf, Component 역할을 모두 수행하는 TagNode 클래스

디자인 패턴은 공통으로 사용할 수 있는 역할, 책임, 협력의 템플릿

 

테스트-주도 개발

등장배경
  • 애자일 방법론의 한 종류인 XP의 기본 프랙티스로 소개되면서 주목받기 시작한 설계 기법
기본흐름
  1. 실패하는 테스트를 작성하고, 테스트를 통과하는 가장 간단한 코드를 작성한다.(이 시간 동안에는 중복이 있어도 무방)
  2. 리팩터링을 통해 중복을 제거

테스트-주도 개발을 통해 작동하는 깔끔한 코드(clean code that works)를 얻을 수 있다.

방법
  • 테스트-주도 개발은 객체가 이미 존재한다고 가정하고 객체에게 어떤 메시지를 전송할 것인지에 관해 먼저 생각하라고 권한다.
  • 단, 역할, 책임, 협력의 관점에서 객체를 바라보지 않을 경우 무의미하다.
특징
  • 책임-주도 설계의 기본 개념을 따른다.
  • 책임-주도 설계를 통해 도달해야 하는 목적지를 테스트라는 안전장치를 통해 좀 더 빠르고 견고한 방법으로 도달할 수 있도록 해주는 최상의 설계 프랙티스
  • 객체지향에 대한 깊이 있는 지식을 요구
    • 테스트 작성을 위한 객체의 메서드를 호출하고 반환값을 검증하는 것은 객체가 수행해야 하는 책임에 관해 생각한 것
    • 테스트에 필요한 간접 입력 값을 제공하기 위해 스텁(stub)을 추가하거나 간접 출력 값을 검증하기 위해 목 객체(mock object)를 추가하는 것은 객체와 협력해야 하는 협력자에 관해 고민한 결과를 코드로 표현한 것
  • 책임-주도 설계의 기본 개념고 ㅏ다양한 원칙과 프랙티스, 패턴을 종합적으로 이해하고 좋은 설계에 대한 감각과 경험을 길러야만 적용할 수 있는 설계 기법

 

 

 

 

 

 

 

 

 

반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...