루비로 배우는 객체지향 디자인 | 4장 유연한 인터페이스 만들기

날짜
May 21, 2024
태그
OOP
설명
메시지 기반의 객체지향 디자인
 
애플리케이션은 클래스로 구성되어 있고, 메시지를 통해 정의된다. 객체의 움직임을 반영하는 것은 메시지이며, 메시지가 애플리케이션을 살아움직이게 한다. 객체가 무엇을 아는지(객체의 책임)와 누구를 알고 있는지(객체의 의존성) 그리고 어떻게 소통하는지(객체의 인터페이스)를 알아야 한다.
 

인터페이스 정의하기

레스토랑의 부엌을 떠올려보자. 손님은 메뉴판을 보고 음식을 주문한다. 주문내역은 홀과 부엌을 이어주는 작은 창을 지나 부엌으로 전달된다. 그리고 음식이 나온다. 부엌에서는 많은 일이 일어나지만 손님은 부엌에서 일어나는 일을 알지 못한다. 식당에는 손님이 사용할 수 있는 메뉴판(퍼블릭 인터페이스)이 있고, 주문은 받은 부엌에서는 손님은 알지 못하는 많은 일(프라이빗 인터페이스)이 일어난다. 클래스는 마치 이런 부엌과 같다. 하나의 책임을 제대로 수행하기 위해 존재하며 수많은 메서드를 구현하고 있다.
 

퍼블릭 인터페이스

  • 클래스의 핵심 책임을 드러낸다.
  • 다른 객체에 의해 호출될 수 있다.
  • 쉽게 변경되지 않는다.
  • 다른 객체가 안정적으로 의존할 수 있다.
  • 테스트를 통해 꼼꼼하게 문서화되어 있다.
 

프라이빗 인터페이스

  • 세부적인 구현을 담당한다.
  • 다른 객체에 의해 호출되지 않는다.
  • 필요에 따라 언제든 변경할 수 있다.
  • 다른 객체가 의존하기에는 위험하다.
  • 테스트에서 다루지 않을 수도 있다.
 

책임, 의존성 그리고 인터페이스

클래스의 퍼블릭 인터페이스는 클래스의 구체적인 책임을 표현한 문장과 같다. 자신보다 덜 의존적인 것에 의존해야한다는 것은 클래스 내부에서도 적용된다. 클래스의 퍼블릭한 부분은 안정적인 부분이다.
 
 

의도를 구성하기

처음 애플리케이션을 구성하려고 마음을 먹었다면 펜과 종이를 들자. 아직은 생각이 정리되지 않은 상태일 것이기 때문이다. 테스트를 작성하는 것은 의도와 디자인이 어느정도 정해진 이후의 이야기이다. 애플리케이션의 명사들, 정보 혹은 행동을 가지고 있는 것을 우리는 ‘도메인 객체’라고 부른다. 예를 들어 여행객, 여행길, 자전거, 정비공 같은 것이다. 의도를 구성하기 위해서는 도메인 객체 뿐만아니라 도메인 객체간 주고 받는 메시지에 주목해야 한다. 새로운 객체를 찾도록 도와주는 가이드 역할을 한다. 디자인 전문가들은 이런 고민을 수없이 해오며 그에 알맞는 도구를 만들었다.
 

시퀀스 다이어그램 사용하기

객체와 메시지를 탐구해볼 수 있는 완벽하고 값싼 방법이 있다. 바로 시퀀스 다이어그램을 사용하는 것이다. 시퀀스 다이어그램은 통합 모델링 언어(UML, Unified Modeling Language)에 정의되어 있으며, UML이 제공하는 많은 다이어그램 중 하나이다.
 
시퀀스 다이어그램으로 객체 사이의 메시지 시각화하기
시퀀스 다이어그램으로 객체 사이의 메시지 시각화하기
 
moe라는 이름의 여행객(Customer) 객체는 여행(Trip) 객체에게 알맞는 여행(suitable_trips)의 메시지를 보내고 결과를 돌려받는다. 이 시퀀스 다이어그램은 의도를 잘 담은 것 처럼 보이나, 디자인에서는 멈칫하게 만든다. 여행객은 Trip 클래스에게 자신에게 어울리는 여행을 찾아주길 바라고 있다. 맞다. 하지만 모리스가 여행에 나서려면 자전거도 필요하고 사전 정비도 필요한데 그 모든 책임을 Trip 클래스에서 해결하려고 하고 있다. 여행에 적당한 자전거가 준비되어 있는지를 파악하는 것이 Trip 클래스의 역할일까? 바로 이 질문에서 시퀀스 다이어그램의 가치가 있다. 객체들이 주고 받는 메시지(퍼블릭 인터페이스)를 명시적으로 확인하며 디자인을 논의하고 수정하게 된다. 그렇다면 이 메시지는 누구에게 전송하는 것이 올바를까? 라고 질문하는 것이 메시지 기반 디자인이다. 클래스 기반 디자인에서 메시지 기반의 전환은 훨씬 유연한 애플리케이션을 만들 수 있게 해준다.
 
여행객은 Trip과 Bicycle 둘 모두와 소통한다.
여행객은 Trip과 Bicycle 둘 모두와 소통한다.
 
지금까지 얻은 사실을 바탕으로 각 객체의 책임에 대해 정리해보면,
 
여행객
  • 여행객은 여행지 목록을 얻고자 한다.
  • suitable_trips 메시지를 구현하고 있는 객체가 있다.
  • 적당한 여행지를 찾기 위해서는 적당한 자전거가 있어야 한다.
  • suitable_bicycle 메시지를 구현하고 있는 또 다른 객체가 있다.
 
전체적으로 보면, Trip에게 집중된 책임은 분산됐지만 단지 Customer에게 돌린것뿐 Customer의 책임은 나아지지 못했다. Customer는 자신에게 맞는 여행과 자전거를 평가할 수 있는 핵심 로직을 알고 있다. 이 말은 즉, Customer가 부엌에서 직접 요리를 하는 것과 같다. 자신의 책임이 아닌 것까지 직접 관리하고 있는 것이다.
 

어떻게(How)해야 하는지 말해주지 말고, 어떤 것(What)을 달라고 요구하기

보내는 객체가 원하는 것을 요구하는 메시지와 받는 객체가 어떻게 행동해야하는지 알려주는 메시지의 차이를 구분하는 것은 매우 중요하다.
 
Trip 객체가 Mechanic에게 자전거를 어떻게 준비해야 하는지 알려준다.
Trip 객체가 Mechanic에게 자전거를 어떻게 준비해야 하는지 알려준다.
  • Trip의 퍼블릭 인터페이스는 bicycles 메서드를 포함하고 있다.
  • Mechanic의 퍼블릭 인터페이스는 clean_bicycle, pump_tire… 메서드를 포함한다.
  • Trip은 clean_bicycle, pump_tire… 메서드를 가지고 있는 객체를 필요로 한다.
 
이 디자인에서 Trip은 Mechanic이 하는 세세한 작업까지 모두 알고 있다. Trip이 Mechanic을 관리하기 때문에 자전거를 준비하는 절차가 추가되면 Trip과 Mechanic 모두 수정되어야 한다.
 
Trip이 Mechanic에게 Bicycle을 준비하라고 요청한다.
Trip이 Mechanic에게 Bicycle을 준비하라고 요청한다.
  • Trip의 퍼블릭 인터페이스는 bicycles 메서드를 포함하고 있다.
  • Mechanic의 퍼블릭 인터페이스는 prepare_bicycle 메서드를 포함하고 있다.
  • Trip에게는 Prepare_bicycle 메서드를 가지고 있는 객체가 필요하다.
 
Trip은 Mechanic에게 여러 책임을 넘겨주었다. 자전거를 준비하는 어떠한 절차가 추가된다고 해도 Mechanic의 내부 메서드가 추가될 뿐, 두 객체 사이의 메시지는 변함이 없다. 메시지 패턴을 바꿔 코드의 유지보수를 수월하게 만들었다. 하지만 Trip은 여전히 Mechanic에 대해 많은 것을 알고 있다.
 

주어진 맥락에서 독립적일 수 있게 하기

Trip이 다른 객체에 대해 알고 있다는 사실은 Trip이 속한 맥락을 구성한다. Trip은 하나의 책임을 가지고 있지만, 특정한 맥락을 필요로 한다. Trip은 prepare_bicycle 메시지에 반응할 수 있는 Mechanic 객체를 필요로 한다가 그 맥락이다. Trip은 맥락이 없이는 책임을 다 하기 힘들다. 예컨데 Trip을 사용하려면 Mechanic이 준비되어 있어야 한다. 맥락은 재사용성과 관련이 깊다. 복잡한 맥락 속에 위치한 객체는 사용도 어렵고, 테스트도 어렵다. 가능한 최고의 상황은 객체가 자신의 맥락으로부터 완전히 독립되어 있는 것이 좋다. 우리는 누구인지 모르는 객체와 협업하는 방법, 즉 의존성을 주입하는 방법으로 이러한 문제를 해결할 수 있다. Trip이 Mechanic이 무엇을 하는지 모르는 채로 Mechanic의 올바른 행동을 호출하도록 만들어야 한다.
 
Trip이 Mechanic에게 여행을 준비하라고 요청한다.
Trip이 Mechanic에게 여행을 준비하라고 요청한다.
  • Trip의 퍼블릭 인터페이스는 bicycles를 포함하고 있다.
  • Mechanic의 퍼블릭 인터페이스는 prepare_trip, prepare_bicycle을 포함한다.
  • Trip은 prepare_trip 메서드에 반응할 수 있는 객체가 필요하다.
  • Mechanic은 prepare_trip의 인자로 bicycles에 반응할 수 있는 객체가 필요하다.
 
이제 정비공이 어떻게 여행을 준비하는지에 대한 지식은 Mechanic 내부에 고립되었다. Trip에 대한 맥락도 줄어 재사용성과 테스트가 용이해졌다.
 

다른 객체를 믿기

지금까지의 과정을 통해 객체의 책임과 의존성의 방향을 조정했다. “객체가 본인이 원하는 것을 알고 있고, 책임을 위임할 객체가 주어진 역할을 제대로 할 것으로 믿고 있다”라는 맹목적인 믿음이 객체지향 디자인의 핵심이다.
 

새로운 객체를 찾아내기 위해 메시지를 사용하기

여행객이 TripFinder에게 자신에게 어울리는 여행이 무엇인지 물어본다.
여행객이 TripFinder에게 자신에게 어울리는 여행이 무엇인지 물어본다.
Customer가 적절한 여행을 찾는 행위는 타당해 보인다. 여행객이 무엇을 원하는지 말해주고 있는것이다. 문제는 수신자에게 있다. 애플리케이션에는 Customer, Trip, Bicycle이 교차하는 지점에서 어울리는 여행을 찾아줄 객체가 필요하다는 사실에 도달한다.
 

자신의 인터페이스를 드러내는 코드 작성하기

테스트보다도 다른 어떤 코드보다도 인터페이스가 중요하다. 인터페이스를 만들때 염두해야 할 기본적인 원칙이 있다. 제대로 작동하고, 다시 사용하기 쉬우며, 미래의 예상치 못한 상황에도 적응할 수 있는 코드를 작성하는 것이 목표이다.
 
퍼블릭 메서드의 일반적인 원칙
  • 일반적인 의미, 엄밀하고 명시적으로 규정되어 있어야 한다.
  • ‘어떻게’보다는 ‘어떤’것에 대해 말하야 한다.
  • 예측할 수 있는 한도에서나마 바뀌지 않을 이름을 지어야 한다.
  • 추가적인 인자를 해시로 받아라.
 

요약

  • 객체지향 애플리케이션은 객체들이 서로 주고받는 메시지를 통해 정의된다.
  • 잘 정의된 퍼블릭 인터페이스는 해당 클래스의 책임을 제대로 드러내준다.
  • 메시지에 집중하면, 미처 파악하지 못했던 객체를 발견할 수 있다.
  • 수신자가 어떻게 행동해야 하는지를 말하지 말고, 원하는 바를 말한다.
 

댓글

guest