2015년 03월 01일 | Steve.Jung

Android 와 개발 패턴

이 포스팅은 총 4부로 이어지며 현재는 1부입니다.

  1. 1부 : Android, MVC, MVVM, MVP
  2. 2부 : Android 와 Annotation
  3. 3부 : AndroidAnnotations 과 MVC
  4. 4부 : AndroidAnnotations 과 테스트

코드를 작성해 가는데 있어서 많은 개발자들은 일관성을 유지하고 유지보수성을 높일 수 있는 많은 개발 모델들을 생각해 왔습니다. 특히나 협업하는 과정에서 로직과 View 를 다루는 코드가 뒤섞이게 되면 작성자뿐만 아니라 동료들조차도 유지보수 하기 힘들어지는 모습을 쉽게 볼 수 있습니다.

이러한 코드 작성때문에 팀단위로, 프로젝트 단위로, 때로는 회사 단위로 여러가지 정책을 가지고 코드와 개발 모델들을 일관되게 하려고 많은 노력을 합니다. 이러한 노력에 개발자들 사이에서 많이 통용되는 개발 패턴들이 나오게 되었습니다.

대표적으로 MVC 모델이라고 할 수 있습니다. 필자가 과거에 했던 웹 프로젝트 중 SpringMVC 프레임워크는 외부로부터의 요청을 처리하는 Controller, 실질적인 비지니스 로직을 수행하는 Model, 그리고 화면 처리를 담당하는 View 를 구분할 수 있도록 지원되어 많은 개발자들에게 사랑받고 있는 프레임워크 중 하나입니다.

차츰 View 자체에서 처리해야하는 로직이 복잡해짐에 따라 MVVM과 MVP 패턴이 나오면서 View 내에서도 Logic과 Presenter 를 구분하려는 노력이 끊임없이 나왔습니다.

이외에도 많은 개발 패턴들이 있지만 가장 많이 통용되는 이야기를 하고자 합니다.

초창기 안드로이드 개발 코드의 모습

안드로이드에서도 초창기 부터 이러한 노력이 끊임없이 나왔습니다. MVC 와 같은 개발 패턴에 익숙해져 있던 많은 개발자들은 안드로이드에도 이와 같은 모습을 적용하려고 노력하였습니다.

다음 코드는 일반적으로 처음 안드로이드를 접하는 사람들이 쓰는 코드들입니다.

public class MainAcivity extends Activity {

	@Override
	public void onCreate(Bundle saveInstance) {
	   super.onCreate(saveInstance);
	   setContent(R.layout.main);

	   TextView textView = (TextView) findViewById(R.id.btn_confirm);
	   textView.setText("Non-Clicked");


	   findViewById(R.id.btn_confirm).setOnClickListener(new View.OnClickListener() {

	       @Override
	       public void onClick(View view) {
	           TextView textView = (TextView) findViewById(R.id.btn_confirm);
	           textView.setText(getClickedText());
	       }
	   });
	}

	String getClickedText() {
	   return "Clicked!!!";
	}

}

Activity 내에 이벤트를 핸들링하는 처리나 뷰에 접근하는 코드들이 모두 있습니다.

이러한 코드들의 모습은 서버기반 동작시엔 하나의 Activity 내에 네트워크 처리를 위한 쓰레드 처리까지 하게되는 등 코드가 커지면 커질수록 가독성도 떨어지며 유지보수가 힘들어지는 코드로 가기 쉬워집니다.

그래서 기존의 웹에서처럼 좀 더 기능별로 분할하여 코드들을 간결하고 유지보수가 쉬워지도록 하기 위한 방법들이 많이 도입되기 시작하였습니다.

나은 코드들을 위한 패턴들

하지만 화면이 점점 복잡해지게 되고 점점 View 에 의해 제어되는 로직들이 많아짐에 따라 이를 분리하고하는 노력들이 나왔습니다.

다음은 웹 서비스 개발자들에게 가장 쉽게 사용되었던 MVC 패턴을 적용한 코드입니다.

  • MVC
public class MainAcivity extends Activity {
	private MainModel mainModel;

	@Override
	public void onCreate(Bundle saveInstance) {
		super.onCreate(saveInstance);
		setContent(R.layout.main);
		
		mainModel = new MainModel();
		
		TextView textView = (TextView) findViewById(R.id.btn_confirm);
		textView.setText("Non-Clicked");
		
		
		findViewById(R.id.btn_confirm)
			.setOnClickListener(new View.OnClickListener(){
			
				@Override
				public void onClick(View view) {
					String text = mainModel.getClickedText();
					TextView textView = (TextView) findViewById(R.id.btn_confirm);
					textView.setText(mainModel.getClickedText());
				}
			});
	}
}
public class MainModel {

	public String getClickedText() {
		return "Clicked!!!";
	}

}

View 에 상관없는 로직을 MainModel 로 분리를 하였기 때문에 Activity 는 View 와 Click Event 를 처리하는 모습으로 변화되었습니다.

하지만 Click Event 와 View 에 대한 처리가 함께 있는 것을 유심히 생각해볼 필요가 있습니다. MVC 모델에서 Controller 는 View 에 대한 처리를 직접 하는 것이 아니라 View 에 대한 정보만을 View 에 전달함으로써 화면을 그리는 View 와 동작을 처리하는 로직을 분리하고자 하는데서 시작되었습니다.

헌데 Controller 의 역할을 수행하는 Activity 에서 View 에 대한 직접적인 조작을 수행하는 것은 MVC 모델에 어긋나는 모습을 보여주게 됩니다.

이는 Android 에서 Activity(Fragment) 가 View 와 Controller 두가지의 특성을 모두 가지고 있기 때문에 View 나 Controller 를 한쪽으로 빼게 될 경우 View 에 대한 바인딩이나 처리에서 중복 코드나 일관성을 잃어버리는 코드를 작성할 수 있기 때문입니다. 이를 개선하기 위해서 MVVM 이란 패턴을 적용해봤습니다.

  • MVVM
public class MainAcivity extends Activity {

	private MainViewModel mainViewModel;

	@Override
	public void onCreate(Bundle saveInstance) {
		super.onCreate(saveInstance);
		setContent(R.layout.main);
		
		mainViewModel = new MainViewModel(MainActivity.this);
		
	}

}
public class MainModel {

	public String getClickedText() {
		return "Clicked!!!";
	}

}
public class MainViewModel {

	private Activity activity;
	private MainModel mainModel;
	private TextView textView;
	
	public MainViewModel(Activity activity) {
		this.activity = activity;
		this.mainModel = new MainModel();
		initView(activity);
	}

	private void initView(Activity activity) {
	
		textView = (TextView) activity.findViewById(R.id.btn_confirm);
		textView.setText("Non-Clicked");
	
		activity.findViewById(R.id.btn_confirm)
			.setOnClickListener(new View.OnClickListener(){
			
				@Override
				public void onClick(View view) {
					String text = mainModel.getClickedText();
					textView.setText(text);
				}
			});
	}
	
}

ViewModel 로 View 에 대한 처리가 분리되었음을 볼 수 있습니다.

하지만 안드로이드에서 MVVM이 가지는 문제점은 View 에 대한 처리가 복잡해질수록 ViewModel 에 거대해지게 되고 상대적으로 Activity 는 아무런 역할도 하지 않는 형태의 클래스로 변모하게 됩니다.

Controller 의 성격을 지닌 Activity 가 실질적으로 아무런 역할을 하지 못하고 ViewModel 에 치중된 모습을 보여줌으로써 다른 형태의 Activity 클래스를 구현한 꼴이 되어버리는 것입니다.

MainViewModel 에 있는 로직을 다시 Activity 로 롤백한다하면 다시 MVC 가 가지고 있는 문제점을 가지게 되는 아이러니한 모습을 가지게 됩니다.

다음은 이러한 View - Model - Controller 의 모습을 명확히 구분하고자 나온 MVP 모델을 보도록 하겠습니다.

  • MVP
public interface MainPresenter {

	void setView(MainPresenter.View view);

	void onConfirm();

	public interface View {
		void setConfirmText(String text);
	}
	
}
public class MainAcivity extends Activity implements MainPresenter.View {

	private MainPresenter mainPresenter;
	
	private Button confirmButton;

	@Override
	public void onCreate(Bundle saveInstance) {
		super.onCreate(saveInstance);
		setContent(R.layout.main);
		
		mainPresenter = new MainPresenterImpl(MainActivity.this);
		mainPresenter.setView(this);
		
		confirmButton = (Button) findViewById(R.id.btn_confirm);
		confirmButton.setOnClickListener(new View.OnClick() {
			@Override
			public void onClick(View view) {
				mainPresenter.onConfirm();
			}
		});
	}
	
	@Override
	public void setButtonText(String text) {
		confirmButton.setText(text);
	}
}
public class MainModel {

	public String getClickedText() {
		return "Clicked!!!";
	}

}
public class MainPresenterImpl implements MainPresenter {

    private Activity activity;
    private MainPresenter.View view;

    public MainPresenterImpl(Activity activity) {
        this.activity = activity;
        this.mainModel = new MainModel();
    }
    
    @Override
    public void setView(MainPresenter.View view) {
    	this.view = view;
    }
    
    @Override
    public void onConfirm() {
    	if (view != null) {
    		view.setConfirmText(mainModel.getClickedText());
    	}
    }
    

}

MVP 모델의 구분은 다음과 같다.

  1. View 는 실제 view 에 대한 직접적인 접근을 담당한다.
  2. view 에서 발생하는 이벤트는 직접 핸들링하나 Presenter 에 위임하도록 한다.
  3. Presenter 는 실질적인 기능을 제어하는 곳으로써 ViewController 로써 이해하면 쉽다.
  4. Model 은 비지니스 로직을 실질적으로 수행한다.

Presenter : View 는 1:1 로 매칭하며 View Presenter 가 주요 기능을 관장하되 실제 view 에서 발생하는 이벤트는 Presenter (이벤트 : View -> Presenter) 로 전달하여 처리하도록 하고 다시 처리된 결과는 Presenter 가 View 에 전달하도록 하여 처리한다. (처리 결과 표현 : Presenter -> View)

정리

 
Summary Image
https://tomyrhymond.wordpress.com/2011/09/16/mvc-mvp-and-mvvm/
  • MVC

외부의 모든 이벤트를 Controller(Activity) 에서 처리하되 View 에 관여는 하지 않는 것이 원칙입니다. 하지만 Activity 의 특성상 View 관여하지 않을 수 없습니다. 결국 Android 에서는 MVC 의 구조를 계속적으로 유지하기에는 무리가 있습니다.

  • MVVM

ViewModel 이 뷰와의 상호작용 및 제어에 집중합니다. ViewModel 에서 직접 뷰의 이벤트를 처리하기 때문에 Controller 의 역할도 함께 수행하게 되어 점점 코드가 집중 되는 구조가 될 수 있습니다. 또한 초기화와 외부 이벤트(뷰에 의한 것이 아닌 이벤트)를 제외 하고는 Activity 의 역할이 모호해지게 됩니다.

  • MVP

View 에 대한 직접적인 접근이 요구되는 Android 의 Activity 는 직접적인 view 접근은 Activity 가 하도록 하고 이에 대한 제어는 Presenter 가 하도록 하고 있다.


어느 것이 낫다라고 말하기에는 어려운 단계입니다. 하지만 많은 개발자들이 안드로이드에서는 MVC 자체의 모습보다는 MVVM 이나 MVP 가 가장 적합하다는 말을 많이 하고 있습니다. 이는 Activity 가 View 를 포함한 클래스이기 때문에 나타나는 현상이라고 볼 수 있습니다.

다음 포스팅에는 이를 해결하기 위해 저희가 사용한 AndroidAnnotations 에 대해 알려드리도록 하겠습니다.

참고 블로그

MVC, MVP, MVVM 의 이해 - 이항희(http://atconsole.com/2013/06/05/mvc-mvp-mvvm-%EC%9D%98-%EC%9D%B4%ED%95%B4/)

MVC, MVP AND MVVM - tomyrhymond(https://tomyrhymond.wordpress.com/2011/09/16/mvc-mvp-and-mvvm/)