Android Architecture - MVC에서 MVP에서 MVVM으로 가는 길



개인 광고 영역

Android MVC/MVP/MVVM가 소개되었고, 다양한 방법으로 이러한 Architecture를 적용하게 됩니다.

구글에서는 테스트 가능한 형태를 만들어주기 위해서 Android Architecture Blueprints을 소개해주었습니다. 하지만 모두가 이러한 방법을 따르는 것은 아닙니다.

이번 글에서는 MVC > MVP > MVVM으로 순차적으로 넘어가게 되었던 내용을 정리해보았습니다.

제 생각을 정리한 글입니다. 단순 참고해주세요. RxJava/data-binding 등의 이야기는 별개입니다.


MVC 패턴

MVC 패턴이라고 말하기 뭐 하지만, 그냥 흔하게 쓰는 패턴입니다.

Activity에서 OnClick이 일어나면 데이터를 불러오고, UI를 갱신하는 아주 흔한 코드입니다.

클래스 몇 개 분리될 수도 있고, 하지 않을 수 있습니다.(당연히 규모가 커지면 분리되는 양이 늘어나겠죠)

가령 아래와 같을 수 있습니다. 몽땅 Activity에 포함되어 있는 코드입니다.

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.recycler_view)
    RecyclerView recyclerView;

    private ImageAdapter imageAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        imageAdapter = new ImageAdapter(this);
        imageAdapter.setImageItems(SampleImageData.getInstance().getImages(this, 10));
        recyclerView.setAdapter(imageAdapter);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
    }
	}
}


처음엔 위와 같이 해볼 수 있습니다. 결국 업무가 많아지고, 화면의 수가 N 개만큼 늘어나면, 복잡도도 증가합니다.


MVC - 테스트 코드 작성?

이 단계에서는 사실 테스트 코드는 생각은 하지만, 현실적으로 구현하기 어렵습니다.

모든 게 화면에 있다 보니 테스트해볼 엄두도 안 납니다.

그래서 그냥 흔적이라도 끄적끄적 해볼 수 있게, 몇 개 추가해봅니다.

하지만 테스트 진행은 더 이상 가능하지 않고, 그냥 폰에 실행해서 직접 손으로 눌러보면서 확인합니다.


MVC를 통해 분리라도 해보자

리펙토링을 통해 중복 코드를 줄여보도록 시도해봅니다.

setTheme/setToolbar 등의 공통 코드를 발견하고, 이를 분리해봅니다.

  • abstract class를 이용해 BaseActivity/BaseFragment를 만들어봅니다.
  • 데이터도 화면에 있으니, Data class를 만들어줍니다.
  • SettingActivity/AboutActivity는 대부분의 화면에서 불리다보니 Util 클래스로 만들어봅니다.

추상화와 Util 클래스를 분리하여 그나마 이쁘게 정리된 느낌입니다.


Package 구조는?

Package 구조는 또 어떻게 가져가는 게 좋을까요?

전통적으로 많이 쓰이고 있는 방법은 아래와 같을 수 있습니다.

package_structure_01

하지만 화면 단위가 많아지고, 그럼 찾기도 쉽지 않습니다.


MVP를 적용해볼까?

Package 구조도 잡아보고, 추상화, Util class를 이용해서 그간 만들었습니다.

MVP라는 구조를 접했습니다. 오 뭔가 있어 보이고, 많은 개발자들이 쓰고 있습니다.

뭔지는 모르지만 View에서 비즈니스 로직을 들어냈다고 합니다.

하지만 처음 느낌은 딱…

뭐가 이렇게 복잡해?? 빙글 빙글?

원래 한눈에 다 파악할 수 있던 Activity에서 Presenter라는 게 추가되었고, 클래스도 2개가 더 추가되었습니다.

  • interface 정의 : View와 Presenter를 정의
  • Presenter : 비즈니스 로직을 가집니다.
  • Activity : 이젠 View의 갱신과 OnClickListener 이벤트만 남습니다.

아래와 같을 수 있습니다.

public class MainPresenter implements MainContract.Presenter {

    private MainContract.View view;

    private SampleImageData sampleImageData;

    @Override
    public void attachView(MainContract.View view) {
        this.view = view;
    }

    @Override
    public void detachView() {
        view = null;
    }

    @Override
    public void setSampleImageData(SampleImageData sampleImageData) {
        this.sampleImageData = sampleImageData;
    }

    @Override
    public void loadItems(Context context, boolean isClear) {
        ArrayList<ImageItem> items = sampleImageData.getImages(context, 10);
        view.addItems(items, isClear);
        view.notifyAdapter();
    }
}


Presenter를 구현 함으로써

기존에는 1개의 클래스에서 처리하던 게 이젠 3개의 클래스가 필요합니다.

하지만 비즈니스 로직이 분리되었고, 이 비즈니스 로직은 테스트 코드도 작성해볼 수 있게 되었습니다.

뭔지 모르지만 점점 눈에 익어갑니다.

오!


MVP의 장/단점은?

MVC에서도 있었던 단점 MVP에서도 있을 수 있습니다.

  • 빙글 빙글

기존에는 View에서 다 처리했지만 View > Presenter > View 이렇게 왔다 갔다 합니다. 음? 뭐 할 때마다 빙글 빙글 돌아가죠. 결국 이건 눈에 익으면 익숙해집니다.

  • 테스트 가능한 코드

UI는 자주 변경되는 점 대비 들어가는 비용이 크기에 테스트하지는 못합니다.

그나마 비즈니스 로직에 대한 테스트 코드를 작성할 수 있게 되었습니다.

강제로 NullPointException도 만들어보고 테스트 코드를 조금씩 추가해봅니다.

기대 이상의 테스트 범위를 가지고, 적용할 수 있게 되었습니다.


하지만 생겨나는 중복 코드들

MVP는 View와 Presenter의 관계가 1:1이어야 합니다. 결국 View 1개에는 Presenter 1개가 무조건 추가된다고 생각하면 되겠습니다.

MVP

그래서 최대 N 개의 View와 N 개의 Presenter를 1:1로 가지고 있어야 합니다.

  • Activity가 N 개 있다면, Presenter도 N 개가 필요할 수 있습니다.
  • Fragment가 N 개 있다면, Presenter도 N 개가 필요할 수 있습니다.

반대로 Presenter가 N개 필요하다는 의미는 아래와 같습니다.

  • 중복적으로 호출하는 구현체가 필요할 수 있습니다.
    • 예) 로그인/로그아웃/특정 아이템을 가져오는 일

중복 코드가 생길 수밖에 없는 녀석입니다.


MVP의 중복 코드를 줄이려면?

이번에도 abstract class를 추가하고, 중복 코드를 줄여볼 수 있습니다.

  • abstract class로 다시 정의(중복 코드를 포함하는 BasePresenter)
  • View를 잘게 잘게 쪼개어 Presenter를 정의(이 경우는 View가 정말 많아질 수 있습니다.)

근본적인 해결 방법은 아니지만 그나마 만족할 수준입니다.


MVP에서의 Package 구조는?

중복 코드 줄이는 것도 중요하지만, Package 구조도 중요하다고 생각합니다.

Android MVP 무작정 따라하기 - Package는 어떻게 할까?에 정리하였던 것 중 마지막 패키지 구조입니다.

package_structure_03

View라는 패키지에 ViewName으로 구분하여, ViewName 들을 여러 개의 패키지 조합으로 묶어둡니다. 그리고 그 아래 Activity/Fragment 등도 다 포함됩니다. Presenter 정의도 포함이죠.

현재도 저는 위와 같은 구조로 정리 중입니다


어서 와 MVVM

MVVM을 생각해볼 수 있게 되었습니다. 구글에서 Android Architecture Component도 소개하였으니, 이제 ViewModel에 대해서 기웃기웃해보겠습니다.

결국 MVP에서 중복 코드를 제거하기 위한 방법 중 하나로 생각하고 저는 ViewModel을 사용하고 있는 형태입니다.

  • 중복 코드 줄이기
  • View와 Presenter의 관계 구성이 1:1을 넘어설 수 있음
  • ViewModel은 여러 개 만들 수 있음

더 이상 View와 Presenter를 1:1로 가져갈 필요가 없습니다. 때에 따라서 ViewModel을 바라보는 View는 N 개가 될 수도 있으며, View에서도 N 개의 ViewModel을 호출해서 사용할 수 있습니다.

미라클~

1:1관계를 벗어남과 동시에 자유로워진 ViewModel입니다.


ViewModel을 정의하는 방법

저의 경우 ViewModel을 아래와 같은 조건으로 분리합니다.

Android MVVM 어떻게 구현하는 게 좋을까?에서 제가 생각하는 ViewModel 정의를 좀 더 자세하게 볼 수 있습니다.

  • 1개 이상의 View에서 호출하는 코드를 우선 찾아 새로운 ViewModel로 분리
  • View에서 사용하던 Presenter는 ViewModel이라는 이름으로 교체.
    • 이름을 바꾸면서 통일성을 가지도록 합니다.
  • ViewModel에서는 View를 들고 있지 않도록 할 수 있습니다.
    • RxJava/data-binding/kotlin higher order function등을 사용해서 의존성을 줄입니다.(알아서 가져가요!!!)
    • View에서 가져다 쓰면서 필요한 코드만을 구현하는 방법으로 접근합니다.

위와 같이 정의하고 ViewModel을 사용하고 있습니다. Presenter보다 훨씬 유연하며, 중복 코드가 발생할 것 같으면 바로 ViewModel로 분리할 수 있습니다.(더 세분화되는 ViewModel)


안드로이드에서 ViewModel을 더 쉽게 쓰는 방법?

2017년 Google I/O에서는 Android Architecture Component를 소개하였습니다. 굳이 적용하거나, 이렇게 쓰세요가 아닌 그냥 이렇게도 할 수 있어요! 수준으로 생각하시면 좋습니다.(알쓸신잡)

Android Architecture Component에서의 ViewModel 정의는 lifecycler을 포함하고 있어, Annotation을 통해 간단하게 라이프사이클 구현도 가능합니다.

결국 이렇 게 좋은 게 있더라도, ViewModel을 어떻게 분리하고 사용할 수 있을지를 알아야 좋을 것 같아서 이렇게 긴 글을 정리해보았습니다.

저는 ViewModel 초기화를 좀 더 쉽게 하려고, 아래 라이브러리를 jcenter()에 배포 중입니다.

compile 'android.arch.lifecycle:extensions:1.0.0-alpha5'
compile 'tech.thdev.lifecycle.extensions:android-lifecycle-extensions:1.0.0-alpha3

관련 글 정리 : Android Architecture Components ViewModel을 간단하게 초기화 하려면?


더 좋은 코드로 가는 방법

결국 많은 패턴을 적용해보는 것도 중요하고, 다양한 경험을 해보는 것도 중요합니다.

더 좋은 코드로 가는 방법은 제가 가장 많이 했던 부분인데 제 코드를 좀 더 좋은 코드로 발전시키기 위해서 더 많은 코드를 보고, 거기에서 얻을 수 있는 장점을 가지고 오는 방법을 택하였습니다.

그러다 보니 기존보다는 더 아름답고, 분리하고, 더 쉬운 코드를 만들고 싶은 목적이 생겼습니다.

그러다 보니 저절로 이런 Architecture에 관심도 생길 수밖에 없었습니다.

현재는 Kotlin을 주로 하고 있으며, MVVM의 조합으로 리펙토링을 진행하고 있습니다.


마무리

Android Architecture Components는 정말 유용한 도구입니다. 알아두면 정말 좋은 것이죠. 하지만 ViewModel을 얼마나 잘 분리할 수 있는지에 대한 기준이 필요하며, 라이프 사이클도 잘 알아야 합니다.

그래야 더 유용하게 사용할 수 있는 것이죠.

이번 글에서는 그간 공부하고, 실제 실무에 적용함으로써 얻을 수 있었던 장/단점(?)을 통해 ViewModel을 쓰게 된 이유를 정리해보았습니다.

그리고 Presenter > ViewModel로 변환하는 과정이 더 적은 시간을 소모합니다.

오히려 MVC > MVVM으로 바로 넘어가면 더 많은 고민을 해야 하지만 이때의 장점은(이미 많이 큰 프로젝트 라면) 코드 분리할 수 있는 게 많이 보인다는 장점입니다.

가장 쉬운 방법은 자신이 아는 방법으로 먼저 짜고 > 리펙토링을 통해 더 좋은 코드로 변환하는 과정이 가장 좋은 학습방법이라고 생각합니다.

아무래도 올해는 ViewModel을 잘 작성하는 방법을 정리해보려고 합니다.

감사합니다.



About Taehwan

My name is Taehwan Kwon. I have developed Android for 6 years and blog has been active for eight years.

Comments