Presenter First 개발
프리젠터 먼저하기(Presenter First) 는 모델-뷰-프리젠터(MVP) 디자인 패턴과 테스트 주도 개발(TDD) 등의 아이디어를 버무린 소프트웨어 개발 방법론입니다. 이에 대해 설명하기에 앞서 먼저 모델-뷰-컨트롤러(MVC) 패턴과 비교하여 모델-뷰-프리젠터(MVP) 개념을 정리하면 대략 다음과 같습니다.
- 모델(Model) : 실제 다루고자 하는 데이터 읽기 / 쓰기. 관찰자(Observer) 패턴처럼 이벤트를 제공하면 더 좋습니다. MVC 패턴에서는 모델이 실제 데이터 뿐 아니라 사용자 인터페이스(View)와 연관된 데이터까지 처리하지만, MVP 패턴에서 모델은 순수하게 데이터만 처리합니다. 즉, 모델은 뷰나 프리젠터(사용자 인터페이스)에 대해서는 알 필요도 없고 알아서도 안됩니다.
- 뷰(View) : 사용자 인터페이스. 데이터 표시 뿐 아니라 사용자 입력까지 모두 처리합니다. MVC 패턴에서는 사용자 입력을 컨트롤러(Controller)에서 처리하지만 MVP 패턴에서는 뷰가 모두 처리합니다. 사용자 이벤트는 프리젠터에게 전달합니다. (관찰자 패턴을 사용합니다) 모델의 데이터가 변경되었다는 이벤트가 발생하면 데이터를 표시합니다. 하지만 초기 MVP 패턴과 달리 요즘에는 뷰와 모델간의 의존성까지 아예 없애버리고, 프리젠터가 디스플레이까지 제어합니다. 프리젠터 먼저하기 방법론에서 특히 이 방식을 이용합니다.
- 프리젠터(Presenter) : 뷰가 전달한 사용자 이벤트에 기반하여 시나리오에 따라 모델 데이터를 조작합니다. MVC 패턴에서 어플리케이션 역할을 하는 부분이며, 사용자 요구사항이 변경되거나 로직이 변경되면 수정이 되어야 하는 부분이기도 합니다. 결과적으로, 모델과 뷰에 대해 알고있는 건 오직 프리젠터 뿐이고, 모델과 뷰는 프리젠터를 모릅니다. 뷰는 모델에 대해 알 수도 있지만, 프리젠터 먼저하기 방법론에서는 이 의존성도 없애버립니다.
뷰를 오버로딩 가능한 추상 인터페이스로 만들고 프리젠터가 이 인터페이스를 사용하도록 하면, 뷰를 구현한(implementation) 객체는 플러그인처럼 교체 가능합니다. 즉, 뷰는 어쩔 수 없이 플랫폼이나 GUI 라이브러리에 의존하게 구현해야 하지만 모델과 프리젠터, 추상 뷰는 이와 상관없이 구현할 수 있습니다. 따라서 쉽게 이식 가능할 뿐 이나라, 하나의 데이터에 대해 여러가지 모양의 사용자 인터페이스를 지원하는 소프트웨어 개발이 쉬워집니다. 물론 모델 역시 추상 인터페이스로 만들고 같은 방식으로 프리젠터가 이 인터페이스를 사용하도록 하면 모델 구현 역시 쉽게 교체 가능한 구조가 됩니다. 그리고, 이러한 추상화는 테스트 주도 개발에도 응용할 수 있습니다.
결과적으로, 프리젠터 먼저하기 방법론에서 모델-뷰-프리젠터의 관계는 다음 그림과 같습니다.
[M] -----> [P] <----- [V]
Model <===== Presenter =====> View
(---> : Events, ===> : Messages)
모델과 뷰는 프리젠터에게 이벤트를 전달하고, 프리젠터는 모델과 객체를 제어하기 위한 메시지를 전달합니다. 즉, 프리젠터만 모델과 뷰에 대해 알고 있을 뿐, 모델과 뷰는 다른 구성 요소를 알지 못합니다. 뷰는 비즈니스 로직에서 완전히 분리하고 모든 로직은 프리젠터에서 처리합니다. 뷰 인터페이스는 가능한 얇고 단순해야 하고, 이상적인 경우 사용자 이벤트를 그대로 프리젠터에게 전달하고, 표시할 내용을 읽고 쓰는 동작만 해야 합니다.
프리젠터에게 전달하는 이벤트는 비즈니스 로직에서 사용하는 용어를 사용하여 전달되어야 합니다. 엔지니어 관점의 이벤트가 아닌 사용자 관점의 이벤트를 사용해야 한다는 점입니다. 예를 들어, 비즈니스 로직이 “추가 버튼을 누르면 텍스트 입력 내용이 할일목록에 추가되어야 함"이라면, 뷰는 “save-button-clicked” 이벤트가 정의되어야 하고, 프리젠터는 이 이벤트가 발생했을때 호출할 핸들러를 연결합니다. 핸들러에서는 뷰의 “get_text()” 함수를 호출하여 텍스트 내용을 얻은 뒤 모델의 “add_to_list()” 함수를 이용해 모델에 추가합니다. 여기서 만일 버튼 대신 예쁜 아이콘으로 뷰가 달라졌다고 이벤트 이름을 “save-image-clicked"로 변경하거나 새로운 이벤트를 추가하면 안됩니다. 사용자가 팝업 메뉴를 원해 메뉴 선택을 이용해 동일한 처리를 하게 되더라도 “save-menu-activated"로 변경하거나 새로 추가할 필요도 없습니다. 또는 실제로 버튼의 “clicked” 이벤트가 아닌 “button-pressed” 이벤트를 사용하더라도 프리젠터에게 전달하는 이벤트 이름은 변경하면 안된다는 점이 중요합니다.
위 예를 조금 더 이어보면, 모델의 “add_to_list()” 함수 내에서는 데이터에 새로운 텍스트 내용을 추가한 뒤 내용이 변경되었음을 알리는 “list-changed” 이벤트를 발생하고, 프리젠터에서 미리 이 이벤트에 연결한 핸들러는 모델의 “get_list()” 함수를 호출해 목록을 얻은 뒤, 뷰의 “set_list()” 함수를 이용해 변경된 데이터를 화면에 표시합니다. 여기까지 설명에서 중요한 점은, 모든 변경사항은 관찰자 패턴처럼 이벤트를 이용해 전달한다는 점이고, 프리젠터가 모델과 뷰 간의 중재 작업을 통해 비즈니스 로직을 완성한다는 점입니다. 만일 할일 목록과 할 일 목록 편집 화면이 분리되어 구현되어 있을 경우 서로 의존성을 가지고 싶지 않을 수도 있습니다. 이런 경우 두 모델을 중재(coordinator)해서 연결하는 새로운 프리젠터, 혹은 어플리케이션 객체를 새로 만들어 이벤트 처리를 하면 두 모델간의 의존성도 사라질 수 있습니다.
프리젠터 먼저하기 방법론은 지금까지 설명한 MVP 패턴으로 프로그램을 할때 프리젠터 객체를 가장 먼저 코딩합니다. 어차피 모델과 뷰 객체의 인터페이스(API)와 이벤트는 프리젠터 객체 구현이 마무리 될때까지 계속 수정되므로, 모델과 뷰는 가짜 객체(Mock Object)로 구현합니다. 가짜 객체라고 해도 프리젠터가 원하는 작업은 실제로 수행하는 것처럼 동작해야 하므로 가능한 단순한 자료구조를 이용해야 합니다. 이렇게 해서 비즈니스 로직을 모두 프리젠터에 구현한 뒤에, 모델과 뷰를 실제로 데이터 베이스에 접근하거나 파일에 읽고 쓰든, GTK+ 위젯 라이브러리를 이용하거나 콘솔 GUI를 이용하든 해서 구현하면 됩니다.
하지만 유닛 테스트 도구를 사용하지 않는다면 뷰 없이 프리젠터 동작을 확인하는 코드를 만들기는 조금 불편합니다. 이런 경우 뷰를 가짜 객체 방식으로 만들더라도 어느 정도 기본적인 사용자 입출력이 가능하도록 만들면서 해야 합니다.
참고한 자료
- “MVP: Model-View-Presenter, The Taligent Programming Model for C++ and Java” , Mike Potel, 1996
- “TWISTING THE TRIAD, The evolution of the Dolphin Smalltalk MVP application framework” , Andy Bower, Blair McGlashan, Tutorial Paper for ESUG 2000
- “Presenter First: Organizing Complex GUI Applications for Test-Drivn Development” , agile, pp. 276-288, AGILE 2006 (AGILE'06), 2006.
- “Big, Complex, and Tested? Just Say ‘When’" , Better Software Magazine February, 2007