아키텍처 패턴
안드로이드 아키텍처 패턴에는 대표적으로 MVC, MVP, MVVM이 있다. 해당 패턴들은 안드로이드만을 위한 것이 아니라 소프트웨어공학의 영역이다. 그중 MVC는 가장 오래된 패턴이며 현재 안드로이드에서 잘 쓰이지 않는다. 그럼 굳이 알아야 하나? 최신 트렌드인 MVVM만 알면 되는 거 아닌가?라고 생각할 수도 있지만 자각하지 못한 채로 MVC를 사용하는 것을 방지할 수 있고 아키텍처 패턴이 어떤 이유로 어떻게 발전된 것인지 알기 위해서는 MVC도 알아두면 좋다고 생각한다.
오래된 패턴이고 프로그래밍 전반에 걸쳐 사용되는 개념이다 보니 안드로이드에 완벽하게 개념들을 대응시켜 이해하기 어려운 부분도 있다고 생각한다. 단순 개념 설명만 읽으면 아 그렇구나 하다가도 이게 무슨 소리인가 싶기도 하다. (또 나만 그런가?!) 또한 패턴 구현에 있어서 해당 패턴의 콘셉트는 따르되 방법은 다양할 수 있기 때문에 문법처럼 이렇게 구현해야 한다라는 명확한 답이 없어서 어려울 수도 있다. 그렇기에 여러 예제를 살펴보는 것도 좋고 개념을 직접 코드로 표현하는 연습이 필요하다고 생각한다.
그렇지만 개념부터 살펴보자. (???)
MVC는 Model, View, Controller의 약자이다. 이 문장만으로는 부족하다. 안드로이드에서 각각에 해당하는 것은 무엇이며 각각의 역할은 무엇인지 알아보자.
앱에서 사용하는 데이터, 상태, 비즈니스 로직을 포함한다. 데이터는 사용자의 이름이나 상품의 가격 등과 같은 말 그대로 데이터를 의미한다. 상태는 UI 상태이다. isEnabled와 같은 플래그로 UI의 상태를 나타낼 수도 있고 데이터의 값이 변경되어 UI가 갱신되어야 한다면 그 또한 상태 변경으로 볼 수 있다. 비즈니스 로직은 앱에 필요한 동작을 수행하기 위해서 데이터를 처리하기 위한 알고리즘이다.
쉽게 말하면 사용자가 보는 화면이다. 즉 UI 요소를 담당한다. 다만 사용자의 입력에 대해 어떤 동작을 해야 하는지 모른다. 예를 들어 버튼만 달랑 하나 있는 UI에서 사용자가 버튼을 눌렀을 때 처리되어야 하는 코드를 포함하지 않는다는 것이다. 특히 안드로이드 MVC에서 View는 레이아웃 파일인 activity_main.xml과 같은 형태이기 때문에 더욱 어떤 동작을 처리하기 위한 코드를 담기 어렵다. 단순히 화면을 보여주는 역할을 하며 해당 컴포넌트에 입력이 있을 때 onClick 속성을 통해 어떤 함수가 호출되어야 하는지 정도를 설정할 수 있다. 실제 동작 처리는 해당 함수에서 하는 것이다.
보통의 MVC 패턴에서는 Model과 View의 가교 역할을 한다. View가 사용자 입력을 받으면 Controller에게 알린다. 그러면 Controller는 적절한 조치를 취하기 위한 동작을 한다. 예를 들면 Model과 상호작용하여 UI 상태를 갱신할 수 있다. 안드로이드에서 이런 역할을 하는 것은 Activity 또는 Fragment이다. Activity 또는 Fragment를 생각해보라. View(레이아웃 파일)와 실제로 연결되고 onClickListener 등을 통해 입력을 받았을 때 처리할 동작을 구현할 수 있다. 이때 Model 인스턴스를 생성하여 필요한 비즈니스 로직을 요청하고 결과 데이터를 가져오면 View와 연결되어 있기 때문에 UI 갱신까지 Controller에서 가능하다. 그렇기 때문에 다시 말하지만 Controller는 Model과 View를 이어주는 가교 역할을 하는 것이다. 다만 안드로이드는 View(xml)과 Controller(Activity, Fragment)를 묶어서 사실상 View라고 볼 수 있기 때문에 명시적으로 이러한 역할을 드러나 보이지 않는다.
위 설명들을 바탕으로 구조를 표현하면 위와 같다. 그림을 보면 View와 Model은 서로 분리되어 있는 것을 알 수 있다. 또한 Model은 종속되는 곳이 없기 때문에 테스트하기 쉽고 재사용하기 용이하다. 그러나 Controller 입장에서는 Model에 대한 의존성이 생기고 View와 매우 강하게 결합된다. 그리고 레거시 코드에서 자주 볼 수 있듯이 Controller에 많은 코드가 쌓이게 될 가능성이 높다.
개념은 아무리 봐도 추상적이고 명확하지 않을 수 있다. MVC를 적용한 예제 코드를 통해 앞선 개념이 실제 어떻게 적용되는지 주의 깊게 살펴보면 도움이 될 것이다.
https://github.com/MyStoryG/Android_MVC_Example
예제 앱은 간단한 메뉴 주문을 콘셉트로 한다. 쉽게 카운팅 앱이라고 생각하면 된다. 두 개의 버튼이 있고 하나는 수량을 감소하는 버튼, 하나는 수량을 증가시키는 버튼이다. 해당 버튼을 누르면 변경된 수량이 버튼 사이에 표시된다. 또한 수량에 따른 합계 가격이 최하단에 표시된다.
[View]
먼저 View를 구성한다. View는 activity_order.xml이다. 사용자와 상호작용하는 것은 두 개의 버튼이고 UI 상태 변경이 필요한 텍스트뷰 또한 두 개이다.
[Controller]
View 통해서 입력을 받은 후 Controller가 처리해야 될 동작은 다음과 같다.
1. 메뉴(아메리카노) 수량 증감 요청 (to Model)
2. 수량 변경에 따른 UI 갱신 (to View)
3. 수량 증감에 따른 합계 가격 증감 요청 (to Model)
4. 수량 변경에 따른 합계 가격 UI 갱신 (to View)
5 ~ 6 : 메뉴 아메리카노 Model과 합계 가격 Model에 대한 인스턴스를 생성한다. (의존성 생김)
9 ~ 12 : View 인스턴스 생성 (의존성 생김)
15 : 감소 버튼 입력을 받으면 수량 데이터를 변경하도록 Model에 요청한다.
16 : 변경된 수량 데이터(UI 상태 변경)를 View에 반영(UI 갱신)한다.
17 : 합계 가격 데이터 변경 요청
18 : 변경된 합계 가격 데이터 View 반영
Controller 내부에는 직접적인 데이터 변경 작업이나 비즈니스 로직이 없다. 모두 Model이 처리하고 있고 변경된 데이터를 반영할 UI 요소를 지정해주는 역할을 한다.
[Model]
Controller 동작을 정리하면 Model에 필요한 것들도 정리된다.
1. 메뉴 가격 (데이터)
2. 메뉴 수량 (데이터)
3. 메뉴 수량 증감 (비즈니스 로직)
4. 합계 가격 (데이터)
5. 합계 가격 증감 (비즈니스 로직)
1 : 다른 음료 메뉴가 추가될 것을 고려하여 Beverage 클래스를 상속받은 Americano 클래스
2 : 아메리카노 가격 설정
4 : 수량 증가 로직 함수 (quantity는 부모 클래스에서 상속)
8 : 수량 감소 로직 함수
2 : 합계 가격 데이터
4 : 합계 가격 증가 로직 함수
8 : 합계 가격 감소 로직 함수
Model을 보면 View와 연결된 부분이 전혀 없다. 그 외에도 의존성 없이 독립적이다. 대신 데이터(totalPrice)를 가지며 데이터 변경(비즈니스 로직)이 가능한 함수로 구성되어 있다. 이렇게 데이터와 가공을 담당하고 있기 때문에 Controller에서는 해당 임무를 Model에 완전히 맡길 수 있고 맡겨야 한다.
위 예제에 CafeLatte 메뉴를 추가한다고 생각해보자. (실제로 추가해보면 더 좋다.) 그러면 Controller 부분에 추가되는 View와 Model 관련 코드가 늘어날 것이다. 이런 식으로 Controller에 코드가 쌓여 비대해질 수 있는 것이 MVC의 큰 단점이다.