안드로이드 Architecture 패턴 Part 1: 모델 뷰 컨트롤러 (Model-View-Controller)

Jong Yun Lee
Nspoons
Published in
9 min readMar 3, 2017

출처 : upday devs, Florina Muntenescu

일년전, 안드로이드 팀의 다수가 upday에서 일하기 시작했고, 어플리케에션은 정교하지 못했으며, 우리는 앱이 더 안정화 되기를 바랐습니다. 우리는 왜 우리의 코드가 그토록 나쁜 모양새가 되었는지 알고자 노력했고, 결국 그 문제의 장본인을 찾았죠: 지속적인 UI의 변화와 우리가 필요한 만큼의 유연성을 지원해주는 아키텍처의 부재가 바로 그 녀석이었습니다. 앱은 이미 여섯달만에 네번이나 재 디자인된 상황이었습니다. 디자인 패턴은 Model-View-Controller가 선택되었으나 본래의 MVC 패턴 구조를 저버리고 금새 그 모양새를 잃어 돌연변이가 되어 버리고 말았죠.

함께 Model-View-Controller 패턴에 대해서 살펴봅시다; 몇년동안 안드로이드에 이 디자인 패턴이 적용되었는지, 어떻게 적용되어야 테스트 가능성(testability)을 높일 수 있을지, 그리고 몇가지 장점과 단점을 살펴보도록 해봅시다.

Model-View-Controller 패턴

비지니스 로직보다 유저 인터페이스 로직이 훨씬 변화기 쉬운 세상에서, 데트크탑과 웹 개발자들은 유저 인터페이스의 기능들을 구분하는 것이 필요했습니다. MVC 패턴이 바로 그들의 해결책이였죠.

  • Model — 데이터 레이어, 비지니스 로직과 네트워크, 데이터베이스 API를 관리하는 역할을 한다
  • View — UI 레이어 — Model로 부터 오는 데이터를 보여준다
  • Controller — 로직 레이어, 유저의 행동에 알림을 받고 필요하다면 모델을 업데이트 합니다.
Model-View-Controller class structure

자, 그림에서 보여지듯이, Controller와 View가 Model에 의존하고 있는 것을 알 수 있습니다: 컨트롤러는 데이터를 업데이트 하기 위해서, 그리고 뷰는 데이터를 받기 위해서 Model과 소통해야 하죠. 하지만, 그 당시에 데트크탑과 웹 개발을 위해서 가장 중요한 것은 모델이 분리되는 것과 UI와 독립적으로 테스트 되어야 한다는 것이었습니다. 그래서 몇가지 MVC의 변형된 버전이 나왔습니다. 가장 잘 알려진 것은 모델이 수동적(passive)이냐, 능동적으로 무엇이 변했는지 알리느냐(actively notifying)에 관한 것이었습니다. 좀더 자세히 살펴 봅시다:

Passive Model

수동적 모델에서는, 컨트롤러가 모델을 변형하는 유일한 녀석입니다. 유저의 동작에 따라서, 컨트롤러는 모델을 수정해야 합니다. 모델이 업데이트 된 후에는, 컨트롤러는 뷰에게 이를 알리고 뷰 또한 업데이트 되어야 합니다. 바로 그 시점에 뷰는 모델로부터 데이터를 요청합니다.

Modevl-View-Controller — passive Model — behavior

Active Model

컨트롤러가 모델을 변경하는 유일한 녀석이 아닌 경우에는, 모델이 뷰에 알리는 방법과 업데이트에 대한 다른 클래스들이 필요합니다. 이것은 Observer 패턴의 도움이 있으면 할 수 있습니다. 모델은 업데이트가 필요한 Observer들의 모음을 가지게 되는 것이죠. 뷰는 observer 인터페이스와 레지스터를 모델에 대한 observer로서 구현합니다.

Model-View-Controller — active Model — class structure

모델이 업데이트 할 때마다, observer 들을 돌아가며 확인해서 update 메소들을 호출합니다. 뷰에서의 이 메소드 구현은 그 다음 모델의 가장 최신 데이터에 대한 요청을 발생시킵니다.

Model-View-Controller — active Model — behavior

안드로이드에서의 Model-View-Controller

2011년 즈음에, 안드로이드가 점점더 유명해 지기 시작할때, 아키텍처에 대한 질문들이 자연스럽게 제기되었습니다. 그당시 MVC가 가장 유명한 UI 패턴중 하나였기 때문에, 개발자들은 안드로이드에도 그것을 적용하려 하였습니다.

만약 여러분이 “MVC를 안드로이드에 적용하는 방법 (How to apply MVC in Android)” 이라고 검색을 한다면, 안드로이드에서 가장 인기있는 답변중 하나는 Activity가 뷰 이자 컨트롤러라는 답변일 것입니다. 돌이켜 봤을때, 이 말은 정말 미친 소리처럼 들리네요! 하지만 그 당시의 주안점음 모델을 테스트 가능하게 만드는 것이었고, 대개 뷰와 컨트롤러의 구현에 대한 선택은 플랫폼에 지극히 의존적이었습니다.

MVC가 안드로이드에 어떻게 적용되어야 하는가

요즘에, MVC패턴을 적용 방법에 대한 질문에 대한 답은 찾기 쉽습니다. Activity들이나, Fragment들 그리고 View들은 MVC 세상의 뷰들입니다. 컨트롤러는 안드로이드의 어떤 클래스들도 상속받지 않고 또 사용하지도 않는 구별된 클래스 이어야 합니다. 모델도 그래야 하구요.

컨트롤러가 뷰에게 업데이트를 요청해야 하기 때문에, 컨트롤러가 뷰에 연결할 때 한가지 문제가 있습니다. 수동적 MVC 모델(passive Model MVC) 구조에서, 컨트롤러는 뷰의 참조(reference)를 가지고 있어야 합니다. 테스트를 염두해 두면서 그렇게 하는 가장 쉬운 방법은, Activity/Fragment/View를 상송받는 BaseView 인터페이스를 만드는 것입니다. 그렇게 되면, 컨트롤러 뷰는 BaseView의 참조를 가지게 되겠죠.

장점

모델 뷰 컨트롤러 패턴은 고려해야 할것들을 분산하는데 많은 도움이 됩니다. 이 장점은 테스트 가능성(testability)를 높여 줄 뿐 아니라, 확장하기 쉽게 만들어 주고, 새로운 것들을 쉽게 구현할 수 있도록 해 줍니다.

모델 클래스들은 안드로이드 클래스에 어떠한 참조도 가지고 있지 않고, 그래서 유닛 테스트(unit test)에 매우 직관적입니다. 컨트롤러도 안드로이드 클래스를 상속받거나 구현(implement)하지 않고 뷰의 참조자(reference)만 가지고 있어야 합니다. 이런 방식으로, 컨트롤러의 유닛 테스팅이 가능하게 되는 것이죠.

만약 뷰들이 단일 책임 법칙 (single responsibility principle)을 준수한다면 그들의 역할은 단순히 모든 유저 이벤트와 모델의 데이터를 보여주기 위해 컨트롤러를 업데이트하는 것이 될 것입니다. 어떤 비지니스 로직도 구현하지 않게 되는 것이죠. 이런 경우에, UI 테스트는 뷰의 기능들을 포함하기에 충분해야 합니다.

단점

뷰가 컨트롤러와 모델에 종속되게 됩니다.

뷰의 모델에대한 종속성은 복잡한 뷰들에 의해서 시작됩니다. 뷰들의 로직을 최소화하기 위해서, 모델은 보여지는 모든 요소들에 대해서 테스트 가능한 메소드들을 제공해야 합니다. 능동적 모델 구현(active Model implementation)에 있어서는, 이것들이 기하급수적으로 클래스들과 메소드들을 증가하는 결과로 이어지고, 모든 데이터들에 대한 Observer들이 필요합니다.

뷰가 컨트롤러와 모델에 의존하다고 할때, UI 로직을 변화시키기 위해서 패턴의 유연성을 떨어뜨면서 몇몇 클래스들을 업데이트 해야 할지도 모릅니다.

누가 UI 로직을 다룰 것인가?

MVC 패턴에 따르면, 컨트롤러는 모델을 업데이트 하고 뷰는 모델로부터 보여줄 데이터들을 얻게 됩니다. 하지만 이중 어떤 녀석이 도대체 데이터를 보여주는 역할을 해야 할까요? 모델일까요? 뷰일까요? 다음 예시를 한번 살펴봅시다: 성과 이름을 가진 User 가 있습니다. 뷰에서 “성, 이름" 이렇게, 유저의 전체 이름을 표시해 주어야 합니다. (e.g. “Doe, John”)

만약 모델의 역할이 단순히 데이터 자체를 제공하는데에 있다면, 뷰의 코드는 다음과 같이 되어야 합니다.

String firstName = userModel.getFirstName();
String lastName = userModel.getLastName();
nameTextView.setText(lastName + ", " + firstName);

이렇게 된다면 UI 로직을 다루는 녀석은 뷰가 되는 것입니다. 하지만 이렇게 되면 UI 로직을 유닛 테스트 하는 것이 불가능해 집니다.

다른 접근으로 모델이 보여저야 하는 정보만 주게 해서 뷰로부터 비지니스 로직을 숨기는 방법이 있습니다. 하지만 그렇게 했을때, 모델이 비지니스 로직과 UI 로직 모두를 다뤄야 하는 결론에 이르게 되죠. 유닛 테스트가 가능하지만, 모델이 암암리에 뷰에 의존하게 되어 버리죠.

String name = userModel.getDisplayName();
nameTextView.setText(name);

결론

안드로이드의 초창기에 모델 뷰 컨트롤러 패턴은 많은 개발자들을 혼란스럽게 한 것 같습니다. 코드를 아주 어렵게 만들었고, 유닛 테스트도 불가능하게 만들었죠.

모델의 뷰에 대한 의존성은 우리의 코드 기반 자체를 위협하고, 리팩토링 할 때 앱 전체를 바꿔야 하는 어마무시한 결과를 초래합니다. 새로운 구조와 접근 방식이 있을까요? 다음 포스팅을 읽어보며 알아보도록 합시다.

--

--