1. 程式人生 > >解析Android的MVC和MVP模式

解析Android的MVC和MVP模式

1.     MVC

         mvc是model、view、controller的縮寫。Android 鼓勵弱耦合和元件的重用,android 中mvc的具體體現如下:

  · 模型(model):用於封裝與應用程式的業務邏輯相關的資料以及對資料的處理方法。“模型”有對資料直接訪問的權力,例如對資料庫的訪問。模型中資料的變化一般會通過一種重新整理機制被公佈。為了實現這種機制,那些用於監視此模型的檢視必須事先在此模型上註冊,從而,檢視可以瞭解資料模型發生的改變。(比較:觀察者模式軟體設計模式))(對資料庫的操作、對網路等的操作都應該在model裡面處理,當然對計算等操作也是必須放在該層的)。

  · 檢視層(view):是應用程式中負責生成使用者介面的部分。也是整個mvc架構中使用者唯一可以看到的一層,接收使用者的輸入,顯示使用者的處理結果。為了實現檢視上的重新整理功能,檢視需要訪問它監視的資料模型(Model),因此應該事先在被它監視的資料那裡註冊。

·控制層(controller):根據使用者的輸入,控制使用者介面資料顯示及更新model物件狀態的部分。onKeyUponKeyMultiple,etc.這些輸入都可能改變業務邏輯資料而引起view的變化。也就是說Event(事件)導致Controller改變了Model或View,或者同時改變兩者。只要Controller改變了Model的資料,所有依賴該資料的View都要更新。

似的,只要Controller改變了View,View會從潛在的Model中獲取資料(沒必要的情況下可能不必獲取)來重新整理自己。

2.     MVP

MVP是MVC模式的演化版本,本篇文章對MVP的介紹目的是幫助大家如何針對一個Activity頁面去編寫出MVP風格的程式碼。

很多程式設計師對於MVP的認識是:“程式碼很清晰,不過增加了很多類”。自己第一次看到MVP的demo時候,看完以後覺得非常nice(但是回過頭來,自己想個例子寫,就頭疼寫不出來)。nice的原因還是因為,這個模式的確讓程式碼的清晰度有了很大的提升。

那麼,提升一般都是對比出來的,回顧下,沒有應用MVP的程式碼結構。很多人說明顯是MVC麼:

·View:對應於佈局檔案

·Model:業務邏輯和實體模型

·Controllor:對應於Activity

看起來的確像那麼回事,但是細細的想想這個View對應於佈局檔案,其實能做的事情特別少,實際上關於該佈局檔案中的資料繫結的操作,事件處理的程式碼都在Activity中,造成了Activity既像View又像Controller。

而當將架構改為MVP以後,Presenter的出現,將Actvity視為View層,Presenter負責完成View層與Model層的互動。現在是這樣的:

·View 對應於Activity,負責View的繪製以及與使用者互動

·Model 依然是業務邏輯和實體模型

·Presenter 負責完成View於Model間的互動

小總結下,也就是說,之所以讓人覺得耳目一新,是因為這次的跳躍是從並不標準的MVCMVP的一個轉變,減少了Activity的職責,簡化了Activity中的程式碼,將複雜的邏輯程式碼提取到了Presenter中進行處理。與之對應的好處就是,耦合度更低,更方便的進行測試

MVCMVP最明顯的區別就是,MVC中是允許ModelView進行互動的,而MVP中很明顯,ModelView之間的互動由Presenter完成。還有一點就是PresenterView之間的互動是通過介面的。

下面通過一些簡單的例子來展示如何編寫MVPdemo

效果圖:


看到這樣的效果,先看下完工後的專案結構:


ok,接下來開始一步一步的編寫思路。

(一)Model

         首先實體類User不用考慮這個肯定有,其次從效果圖可以看到至少有一個業務方法login(),這兩點沒什麼難度,我們首先完成:

package com.zhy.blogcodes.mvp.bean;

/**

 * Created by zhy on 15/6/18.

 */

public class User

{

    private String username ;

    private String password ;

    public String getUsername()

    {

       return username;

    }

    public void setUsername(Stringusername)

    {

       this.username = username;

    }

    public String getPassword()

    {

       return password;

    }

    public void setPassword(Stringpassword)

    {

       this.password = password;

    }

}

package com.zhy.blogcodes.mvp.biz;

/**

 * Created by zhy on 15/6/19.

 */

public interface IUserBiz

{

    public void login(Stringusername, String password, OnLoginListener loginListener);

}

package com.zhy.blogcodes.mvp.biz;

import com.zhy.blogcodes.mvp.bean.User;

/**

 * Created by zhy on 15/6/19.

 */

public class UserBizimplements IUserBiz

{

    @Override

    public void login(final String username, finalString password, final OnLoginListenerloginListener)

    {

       //模擬子執行緒耗時操作

       new Thread()

       {

           @Override

           public voidrun()

           {

                try

                {

                    Thread.sleep(2000);

                } catch(InterruptedException e)

                {

                   e.printStackTrace();

                }

                //模擬登入成功

                if("zhy".equals(username) &&"123".equals(password))

                {

                    User user = new User();

                    user.setUsername(username);

                    user.setPassword(password);

                   loginListener.loginSuccess(user);

                } else

                {

                   loginListener.loginFailed();

                }

           }

       }.start();

    }

}

package com.zhy.blogcodes.mvp.biz;

import com.zhy.blogcodes.mvp.bean.User;

/**

 * Created by zhy on 15/6/19.

 */

public interface OnLoginListener

{

    void loginSuccess(User user);

    void loginFailed();

}

實體類不用說,至於業務類,我們抽取了一個介面,一個實現類這也很常見~~login方法,一般肯定是連線伺服器的,是個耗時操作,所以我們開闢了子執行緒,Thread.sleep(2000)模擬了耗時,由於是耗時操作,所以我們通過一個回撥介面來通知登入的狀態。

其實這裡還是比較好寫的,因為和傳統寫法沒區別。

(二) View

上面我們說過,Presenter與View互動是通過介面。所以我們這裡需要定義一個ILoginView難點就在於應該有哪些方法,我們看一眼效果圖:


可以看到我們有兩個按鈕,一個是login,一個是clear;

login說明了要有使用者名稱、密碼,那麼對應兩個方法:

   String getUserName();

   String getPassword();

再者login是個耗時操作,我們需要給使用者一個友好的提示,一般就是操作ProgressBar,所以再兩個:

    void showLoading();

    void hideLoading();

login當然存在登入成功與失敗的處理,我們主要看成功我們是跳轉Activity,而失敗可能是去給個提醒:

    void toMainActivity(User user);

    void showFailedError();

ok,login這個方法我們分析完了~~還剩個clear那就簡單了:

    void clearUserName();

    void clearPassword();

綜上,介面完整為:

package com.zhy.blogcodes.mvp.view;

import com.zhy.blogcodes.mvp.bean.User;

/**

 * Created by zhy on 15/6/19.

 */

public interface IUserLoginView

{

   String getUserName();

   String getPassword();

    void clearUserName();

    void clearPassword();

    void showLoading();

    void hideLoading();

    void toMainActivity(User user);

    void showFailedError();

}

         有了介面,實現就太好寫了~~~

        總結下,對於View的介面,去觀察功能上的操作,然後考慮:

·該操作需要什麼?(getUserName, getPassword)

·該操作的結果,對應的反饋?(toMainActivity, showFailedError)

·該操作過程中對應的友好的互動?(showLoading, hideLoading)

        下面貼一下我們的View的實現類,哈,其實就是Activity,文章開始就說過,MVP中的View其實就是Activity

package com.zhy.blogcodes.mvp;

import android.os.Bundle;

import android.support.v7.app.ActionBarActivity;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

import android.widget.ProgressBar;

import android.widget.Toast;

import com.zhy.blogcodes.R;

import com.zhy.blogcodes.mvp.bean.User;

import com.zhy.blogcodes.mvp.presenter.UserLoginPresenter;

import com.zhy.blogcodes.mvp.view.IUserLoginView;

public class UserLoginActivityextends ActionBarActivityimplements IUserLoginView

{

    private EditText mEtUsername, mEtPassword;

    private Button mBtnLogin, mBtnClear;

    private ProgressBar mPbLoading;

    private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this);

    @Override

    protected void onCreate(BundlesavedInstanceState)

    {

       super.onCreate(savedInstanceState);

       setContentView(R.layout.activity_user_login);

       initViews();

    }

    private void initViews()

    {

       mEtUsername = (EditText) findViewById(R.id.id_et_username);

       mEtPassword = (EditText) findViewById(R.id.id_et_password);

       mBtnClear = (Button) findViewById(R.id.id_btn_clear);

       mBtnLogin = (Button) findViewById(R.id.id_btn_login);

       mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading);

       mBtnLogin.setOnClickListener(newView.OnClickListener()

       {

           @Override

           public voidonClick(View v)

           {

                mUserLoginPresenter.login();

           }

       });

       mBtnClear.setOnClickListener(newView.OnClickListener()

       {

           @Override

           public voidonClick(View v)

           {

                mUserLoginPresenter.clear();

           }

       });

    }

    @Override

    public String getUserName()

    {

       returnmEtUsername.getText().toString();

    }

    @Override

    public String getPassword()

    {

       returnmEtPassword.getText().toString();

    }

    @Override

    public void clearUserName()

    {

       mEtUsername.setText("");

    }

    @Override

    public void clearPassword()

    {

       mEtPassword.setText("");

    }

    @Override

    public void showLoading()

    {

       mPbLoading.setVisibility(View.VISIBLE);

    }

    @Override

    public void hideLoading()

    {

       mPbLoading.setVisibility(View.GONE);

    }

    @Override

    public void toMainActivity(Useruser)

    {

       Toast.makeText(this,user.getUsername() +

                "login success , to MainActivity", Toast.LENGTH_SHORT).show();

    }

    @Override

    public void showFailedError()

    {

       Toast.makeText(this,

                "loginfailed", Toast.LENGTH_SHORT).show();

    }

}

對於在Activity中實現我們上述定義的介面,是一件很容易的事,畢竟介面引導我們去完成。

最後看我們的Presenter。

(三)Presenter

        Presenter是用作Model和View之間互動的橋樑,那麼應該有什麼方法呢?其實也是主要看該功能有什麼操作,比如本例,兩個操作:loginclear

package com.zhy.blogcodes.mvp.presenter;

import android.os.Handler;

import com.zhy.blogcodes.mvp.bean.User;

import com.zhy.blogcodes.mvp.biz.IUserBiz;

import com.zhy.blogcodes.mvp.biz.OnLoginListener;

import com.zhy.blogcodes.mvp.biz.UserBiz;

import com.zhy.blogcodes.mvp.view.IUserLoginView;

/**

 * Created by zhy on 15/6/19.

 */

public class UserLoginPresenter

{

    private IUserBiz userBiz;

    private IUserLoginView userLoginView;

    private Handler mHandler = new Handler();

    public UserLoginPresenter(IUserLoginViewuserLoginView)

    {

       this.userLoginView = userLoginView;

       this.userBiz = new UserBiz();

    }

    public void login()

    {

       userLoginView.showLoading();

       userBiz.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener()

       {

           @Override

           public voidloginSuccess(final User user)

           {

                //需要在UI執行緒執行

                mHandler.post(new Runnable()

                {

                    @Override

                    publicvoid run()

                    {

                       userLoginView.toMainActivity(user);

                       userLoginView.hideLoading();

                    }

                });

           }

           @Override

           public voidloginFailed()

           {

                //需要在UI執行緒執行

                mHandler.post(new Runnable()

                {

                    @Override

                    publicvoid run()

                    {

                       userLoginView.showFailedError();

                       userLoginView.hideLoading();

                    }

                });

           }

       });

    }

    public void clear()

    {

       userLoginView.clearUserName();

       userLoginView.clearPassword();

    }

}

         注意上述程式碼,我們的presenter完成二者的互動,那麼肯定需要二者的實現類。大致就是從View中獲取需要的引數,交給Model去執行業務方法,執行的過程中需要的反饋,以及結果,再讓View進行做對應的顯示。

         參考http://blog.csdn.net/lmj623565791/article/details/46596109