pinkred's mobile program

pinkred mobile programer

Archive for 4월 2016

[Android] MVP Design Pattern

with 2 comments

많은 분야에서 설계에 대해서는 중요성은 항상 강조된다.

모든 코딩은 시간대비 효율성을 따지면서 설계가 되어야 하지 않을까 생각된다.

배보다 배꼽이 더큰 경우도 발생하기도 하기 때문이다.

설계의 여러가지 이슈가 있지만 설계를 하는 몇가지 이유들 중 가장 와닿는 이슈는 아래와 같다.

  1. 협업
  2. 유지보수

협업은 많은 프로그래머들이 함께 하기에 서로 다른 개성으로 코드가 복잡해지기 때문에 약속이 꼭 필요하다. 설계가 잘되어 있어야 업무 배분 및 상대방 코드 이해에 도움이 된다.

업데이트는 다른 코드에 적은 영향을 주면서 편리하게 수정될수 있도록 설계 되어야지 이슈들이 적게 발생한다.

여기까지는 누구나 기본적으로 알고 있는 이야기이다. 원래 서론이 그렇다.

적지 않은 설계 방식이 있는데 안드로이드에서 적합하게 적용할수 있는 모델들을 고민했었다.

실제로는 내가 적합하게 쓸 모델이 될수도 있다. 기본적을 MVC모델을 많이 고려하고 있다. 물론 필자도 해당 패턴에 대해서 어떻게 안드로이드에 적용할수 있을지 고민도 했고 같은 MVC모델 이라고 해도 프로그래머들이 해석하기에 따라서 대부분 약간씩 차이가 있다.

그러던 와중에 MVP모델을 고민하게 되었고 이 모델이 제가 생각하기에는 안드로이드에서 사용하기에 적합하다고 할수 있었다. 물론 주관적인 판단이다.

그럼 어떻게 적용해서 코드가 완성되는지 살펴 보도록 하자.

제가 생각한 안드로이드에서 MVP 모델의 핵심은 Activity에 있다. Activity는 항상 많은 양의 소스코드를 가지고 있어 소스분석에 힘들다. 왜냐하면 Activity는 View, Controller가 합해져 있는 모습을 띄고 있기 때문이다. 그렇다 보니 소스가 복잡하면 길어지는 것은 당연하다.

1. Activity에서 View를 제거한다.

하나의 화면을 아주 큰 Layout이라고 생각한다. 그리고 분리한다. 우리고 TextView를 설정하듯이 Layout을 설정할수 있도록 구성한다.

Activity -> Layout 호출시에는 메소드 호출로 코딩하고, Layout->Activity는 Listener로 호출한다. 구조는 기존의 안드로이드 위젯과 동일하다.  Layout에는 어떤 비즈니스 로직이 들어가면 안된다.

이제 Activity에서 View가 사라져서 상당히 간편해 졌다.

2. 네트워크를 이용한다면 Activity에서 Network Callback을 제거해라.

기존에 View를 제거한것과 동일한다. Network관련 소스가 Activity에서 제거 됨으로 더욱더 깔끔해진다. Actviity->Network 호출시에는 메소드 호출이고, Network응답은 Listener로 응답받는다.

View Layout <–> Activity <–> Network Controller

위와 같이 구성됨으로서 Activity는 컨트롤러 역할만 하게 되고, View, Network 소스가 분리됨으로써 구조가 간단해 진다. 물론 Dialog, Toast 같은 경우에는 View Layout에서 할수 없는 경우가 있어 Activity가 처리하여 조금은 GUI에 관여하지만 이것역시 또다른 View Layout(Dialog, Toast)을 호출했다고 생각하면 된다.

참고로 위와 같이 구성하게 되면 너무 단순한 화면은 오히려 코드 생산성이 줄기도 한다. 배보다 배꼽이 더 큰 경우이다. 필자가 해보니 대략 200~250라인 정도의 소스코드 량이면 Activity에서 전부 해결하고 이상이 되면 분리해주는 것이 보기 편하고 이해도 잘되었다.

소스코드로 대략 형태를 보여드리도록 하겠습니다.

public class MVPActivity extends AppCompatActivity
{
    private MVPLayout mMVPLayout;
    private MVPNetworkController mMVPNetworkController;

    public interface OnEventListener 
    { 
        void finish(); 
    }

    public interface OnNetworkControllerListener 
    { 
        void onError(Exception e);
    }

    public static Intent newInstance(Context context)
    {
        Intent intent = new Intent(context, MVPActivity.class);
        return intent;
    }

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        mMVPLayout = new mMVPLayout(this, mOnEventListener);
        mMVPNetworkController = new MVPNetworkController(this, mOnNetworkControllerListener);

        setContentView(mMVPLayout.onCreateView(R.layout.activity_main));
    }

    private OnEventListener mOnEventListener = new OnEventListener()
    {
        @Override
        public void finish()
        {

        }
    }

    private OnNetworkControllerListener mOnNetworkControllerListener = new OnNetworkControllerListener()
    {
        @Override
        public void onError(Exception e)
        {

        }
    }
}

설명 드린 바와 같이 Activity에서 UI, Network가 빠져나갔다. 그리고 Listener만 존재하게 된다. Activity는 Network를 통해서 받은 내용을 UI에 적용하게 된다. 또한 UI에서 발생하는 이슈를 처리하거나 네트워크를 호출하기도 한다.


MVPLayout.java

public class MVPLayout
{
    protected Context mContext;
    protected View mRootView;
    protected OnEventListener mOnEventListener;

    public MVPLayout(Context context, OnEventListener mOnEventListener)
    {
        if (context == null || listener == null)
        {
            throw new NullPointerException();
        }

        mContext = context;
        mOnEventListener = listener;
    }

    public final View onCreateView(int layoutResID)
    {
        mRootView = LayoutInflater.from(mContext).inflate(layoutResID, null, false);

        initLayout(mRootView);

        return mRootView;
    }

    protected void initLayout(View view)
    {

    }
}

Layout은 Resources생성은 Fragment와 비슷한 방식으로 진행한다. 이렇게 되면 기존에 화면에서 간혹  setConetntView를 한후에 findView를 실패하는 경우가 있을수 없다.

MVPNetworkController.java

public class MVPNetworkController
{
    private Context mContext;
    private OnNetworkControllerListener mListener;

    public MVPNetworkController(Context context, OnNetworkControllerListener listener)
    {
        if (context == null || listener == null)
        {
            throw new NullPointerException();
        }

        mContext = context;
        mOnNetworkControllerListener = listener;
    }

    public void requestList()
    {
        // Network Call
    }
}

오랜 시간동안 고민해보면서 만든 구조 입니다. 중요한 부분은 Layout에 비즈니스 로직과 UI로직을 구분해서 구현하는것이 중요합니다. 물론 구분은 본인들의 판단이 될수 있습니다. 이렇게 하면 파일이 많이 생기는 이슈가 있기는 하지만 서로 독립적으로 구현이 되어 추후 유지 보수 시에도 검증 및 수정도 쉽다는 것을 알수 있습니다.

위의 내용은 기초적인 코드만 공개한 내용입니다.

감사합니다.

 

Written by pinkredmobile

2016/04/21 at 9:02 am