안드로이드 개발을 처음 시작하면 대부분 hello,world 프로그램으로_beginning한다. Android Studio에서 새로운 프로젝트를 생성할 때 기본 설정으로 진행하면 자동으로 실행 가능한 hello,world 애플리케이션이 만들어진다. 이 상태를 MVC 패턴으로 분석해보면 다음과 같은 대응 관계를 가진다:
- View: 레이아웃 XML 파일
- Model: 비즈니스 로직과 엔티티 모델
- Controller: Activity
그러나 데이터 바인딩과 이벤트 처리 코드가 모두 Activity에 포함되면서, Activity는 View와 Controller의 역할을 동시에 수행하게 된다. 프로그램의 비즈니스 로직이 복잡해질수록 Activity의 코드가 점점 많아지고, 결과적으로 결합도가 높아져 유지보수가 어려워진다. 이러한 문제점을 해결하기 위해 등장한 것이 MVP 패턴이다.
가장 간단한 예제를 통해 MVP 패턴의 핵심을 살펴보자.
레이아웃 구성
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.sample.mvpdemo.MainActivity">
<TextView
android:id="@+id/tv_display"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<Button
android:id="@+id/btn_execute"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="실행"/>
</LinearLayout>
화면에는 TextView 하나와 Button 하나가 있다. Button을 누르면 10초간 카운트다운을 진행한 후, TextView에 "Hello MVP!"를 표시하는 간단한 프로그램을 만들어보자.
MVP 패턴 구현 과정
MVP 패턴의 핵심은 Activity에서 Controller 역할을 분리하고, Presenter가 View와 Model 사이의桥梁 역할을 수행하게 하는 것이다. 이를 위해 인터페이스 콜백 방식을 사용한다.
1단계: View 인터페이스 정의
TextView에 표시할 내용을 전달하기 위한 View 인터페이스를 만든다:
package com.sample.mvpdemo.view;
public interface IDisplayView {
void displayText(String message);
}
2단계: Activity에서 View 인터페이스 구현
package com.sample.mvpdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements IDisplayView, View.OnClickListener {
private TextView mTvDisplay;
private Button mBtnExecute;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvDisplay = (TextView) findViewById(R.id.tv_display);
mBtnExecute = (Button) findViewById(R.id.btn_execute);
mBtnExecute.setOnClickListener(this);
}
@Override
public void displayText(String message) {
mTvDisplay.setText(message);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_execute) {
// 나중에 presenter 연결
}
}
}
3단계: Presenter 인터페이스 정의
package com.sample.mvpdemo.presenter;
public interface IActionPresenter {
void execute();
}
4단계: Presenter 구현 클래스 작성
package com.sample.mvpdemo.presenter;
import android.os.AsyncTask;
import android.os.SystemClock;
import android.util.Log;
import com.sample.mvpdemo.view.IDisplayView;
public class ActionPresenterImpl implements IActionPresenter {
private IDisplayView mDisplayView;
public ActionPresenterImpl(IDisplayView displayView) {
this.mDisplayView = displayView;
}
@Override
public void execute() {
new AsyncTask<Void, Void, Void>() {
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Log.d("demo", "작업 완료");
mDisplayView.displayText("Hello MVP!");
}
@Override
protected Void doInBackground(Void... params) {
for (int i = 0; i < 10; i++) {
Log.d("demo", "진행 중... 남은 시간: " + (10 - i) + "초");
SystemClock.sleep(1000);
}
return null;
}
}.execute();
}
}
presenter 구현 클래스에서 AsyncTask를 사용하여 백그라운드에서 작업을 수행한다. 작업이 완료되면 View 인터페이스의 메서드를 호출하여 결과를 전달하는 전형적인 인터페이스 콜백 패턴을 보여준다.
5단계: Activity와 Presenter 연결
package com.sample.mvpdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.sample.mvpdemo.presenter.IActionPresenter;
import com.sample.mvpdemo.presenter.ActionPresenterImpl;
import com.sample.mvpdemo.view.IDisplayView;
public class MainActivity extends AppCompatActivity implements IDisplayView, View.OnClickListener {
private TextView mTvDisplay;
private Button mBtnExecute;
private IActionPresenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvDisplay = (TextView) findViewById(R.id.tv_display);
mBtnExecute = (Button) findViewById(R.id.btn_execute);
mBtnExecute.setOnClickListener(this);
mPresenter = new ActionPresenterImpl(this);
}
@Override
public void displayText(String message) {
mTvDisplay.setText(message);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_execute) {
mPresenter.execute();
}
}
}
이상이 MVP 패턴의 기본 구조이다. Activity는 이제 View의 역할만 담당하며, 실제 비즈니스 로직은 Presenter가 관리한다.
정리
MVP 패턴을 적용하면 인터페이스가 늘어나지만, 코드 로직이 훨씬 명확해진다. 복잡한 화면과 로직을 처리해야 할 때 각각의 기능을 Presenter로 분리하면 코드를 이해하기 쉽고 확장하기也变得方便하다. 그러나 간단한 기능이라면 MVP 패턴을 적용할 필요가 없을 수도 있다. 중요한 것은 상황과业务 요구사항에 맞게 적절한アーキ텍처를 선택하는 것이다.