Android--MVP模式--簡(jiǎn)介

MVC模式

MVC全名是Model View Controller糠涛,是模型(model)-視圖(view)-控制器(controller)的縮寫(xiě),一種軟件設(shè)計(jì)規(guī)范兼犯。

  • Model(模型) 表示應(yīng)用程序的核心
  • View(視圖) 顯示數(shù)據(jù)
  • Controller(控制器) 處理輸入

在安卓工程中:

  • Layout ---> View(視圖層)
  • Activity ---> Controller,View
  • 各種實(shí)體類(lèi) --->Model

在MVC模式中,一個(gè)activity對(duì)應(yīng)著Controller和View集漾,既要處理輸入切黔,又要和視圖打交道,這個(gè)時(shí)候具篇,activity的職責(zé)就會(huì)有多方面(耦合度過(guò)高)


MVC模式圖解

下面我們來(lái)看下MVC模式下的一個(gè)登陸的處理吧纬霞。
在這里,只有一個(gè)Model和Activity驱显,在activity中處理各種登陸判斷邏輯诗芜,和更改view的邏輯瞳抓。

Model層:

package com.example.mvpdemo.model;

/**
 * Created by 若蘭 on 2016/1/13.
 * 一個(gè)懂得了編程樂(lè)趣的小白,希望自己
 * 能夠在這個(gè)道路上走的很遠(yuǎn)伏恐,也希望自己學(xué)習(xí)到的
 * 知識(shí)可以幫助更多的人,分享就是學(xué)習(xí)的一種樂(lè)趣
 * QQ:1069584784
 * csdn:http://blog.csdn.net/wuyinlei
 */

public class UserEntity {

    public String nickName;
    public int userID;
    public String address;
}

View + Controller層:

package com.example.mvpdemo.activity;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.example.mvpdemo.R;
import com.example.mvpdemo.model.UserEntity;
import com.example.mvpdemo.presenter.LoginPresenter;
import com.example.mvpdemo.view.LoginView;
import com.google.gson.Gson;

public class MainActivity extends AppCompatActivity implements LoginView{
    private EditText etUserName;
    private EditText etPassword;
    private Button btn;
    private TextView tvResult;
    private String result;
    private UserEntity mUserEntity;

    private String userName;
    private String pwd;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mUserEntity = new UserEntity();
        mPresenter = new LoginPresenter(this);
        initialize();
    }

    /**
     * 初始化布局控件
     */
    private void initialize() {

        etUserName = (EditText) findViewById(R.id.etUserName);
        etPassword = (EditText) findViewById(R.id.etPassword);
        btn = (Button) findViewById(R.id.btn);

        tvResult = (TextView) findViewById(R.id.tvResult);

        userName = etUserName.getText().toString();
        pwd = etPassword.getText().toString();

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                login(etUserName.getText().toString(), etPassword.getText().toString());

                if (!TextUtils.isEmpty(result)) {
                //解析json數(shù)據(jù)
                    mUserEntity = new Gson().fromJson(result, UserEntity.class);
                    tvResult.setText(mUserEntity.address.toString());
                }
            }
        });
    }

    /**
     * 登錄邏輯
     * @param userName
     * @param pwd
     */
     
    private void login(String userName, String pwd) {
        if (TextUtils.isEmpty(userName)) {
            etUserName.setError("用戶名不能為空");
            return;
        }
        if (TextUtils.isEmpty(pwd)) {
            etPassword.setError("密碼不能為空");
            return;
        }
        if ("123".equals(pwd)) {

            result = "{\n" +
                    "    \"nickName\": \"小明\",\n" +
                    "    \"userID\": \"1212\",\n" +
                    "    \"address\": \"河南周口\"\n" +
                    "}";

        } else {
            Toast.makeText(this, "密碼錯(cuò)誤", Toast.LENGTH_SHORT).show();
            return;
        }
    }

}

View層:

<?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:orientation="vertical"
    android:layout_height="match_parent"
    tools:context=".activity.MainActivity">

    <EditText
        android:id="@+id/etUserName"
        android:layout_gravity="center"
        android:hint="請(qǐng)輸入手機(jī)號(hào)"
        android:padding="@dimen/activity_horizontal_margin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <EditText
        android:id="@+id/etPassword"
        android:hint="請(qǐng)輸入密碼"
        android:inputType="textPassword"
        android:layout_gravity="center"
        android:padding="@dimen/activity_horizontal_margin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/btn"
        android:text="登錄"
        android:layout_gravity="center"
        android:layout_marginTop="100dp"
        android:gravity="center"
        android:padding="@dimen/activity_horizontal_margin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/tvResult"
        android:text="點(diǎn)擊登錄孩哑,獲取用戶信息"
        android:padding="@dimen/activity_horizontal_margin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

在這里我們看到了,在activity中翠桦,由于有了Controller和View層的混淆横蜒,造成了activity的職責(zé)過(guò)多,既要處理控制销凑,又要更新view丛晌。

于是在MVC模式的發(fā)展下,又給衍生出了MVP模式


MVP模式圖

MVP 也就是Model,View,Presenter(交互器/表示器)斗幼,是從經(jīng)典的模式MVC演變而來(lái)澎蛛,它們的基本思想有相通的地方:

  • Controller/Presenter負(fù)責(zé)邏輯的處理,Model提供數(shù)據(jù)蜕窿,View負(fù)責(zé)顯示谋逻。作為一種新的模式。

MVP設(shè)計(jì)原則:

  • 1:一個(gè) Activity對(duì)應(yīng)一個(gè) View
  • 2:通常情況下渠羞,一個(gè) View對(duì)應(yīng)一個(gè) Presenter斤贰,在業(yè)務(wù)復(fù)雜時(shí),一個(gè) View可對(duì)應(yīng)多個(gè) Presenter.
  • 3.將 View 與 Presenter抽象成接口次询。

MVP與MVC有著一個(gè)重大的區(qū)別:

  • 在MVP中View并不直接使用Model荧恍,它們之間的通信是通過(guò)Presenter (MVC中的Controller)來(lái)進(jìn)行的,所有的交互都發(fā)生在Presenter內(nèi)部屯吊,而在MVC中View會(huì)直接從Model中讀取數(shù)據(jù)而不是通過(guò) Controller送巡。

  • 在MVP模式里,View只應(yīng)該有簡(jiǎn)單的Set/Get的方法盒卸,用戶輸入和設(shè)置界面顯示的內(nèi)容骗爆,除此就不應(yīng)該有更多的內(nèi)容,絕不容許直接訪問(wèn)Model--這就是與MVC很大的不同之處

接下來(lái)蔽介,我們看下上述工程按照MVP模式怎么寫(xiě)吧摘投,由于在MVP模式中,禁止view和model的通信虹蓄,而是間接的通過(guò)Presenter通信(這個(gè)時(shí)候要面向接口編程)
那就來(lái)看下VIEW層的封裝犀呼。

package com.example.mvpdemo.view;

/**
 * Created by 若蘭 on 2016/1/13.
 * 一個(gè)懂得了編程樂(lè)趣的小白,希望自己
 * 能夠在這個(gè)道路上走的很遠(yuǎn)薇组,也希望自己學(xué)習(xí)到的
 * 知識(shí)可以幫助更多的人,分享就是學(xué)習(xí)的一種樂(lè)趣
 * QQ:1069584784
 * csdn:http://blog.csdn.net/wuyinlei
 */

/**
 * view層,在這里只有view的表達(dá)
 */
public interface LoginView {

    String getUerName();
    String getPwd();

    /**
     * 處理用戶名沒(méi)有輸入為空
     */
    void onUserNameError();

    /**
     * 處理密碼沒(méi)有輸入為空
     */
    void onPwdError();
    
    /**
     * 處理用戶密碼輸入錯(cuò)誤
     */
    void onError();

    /**
     * 成功登錄
     * @param address
     */
    void onSuccess(String address);
}

接下來(lái)我們來(lái)看下Model層外臂。

package com.example.mvpdemo.model;

/**
 * Created by 若蘭 on 2016/1/13.
 * 一個(gè)懂得了編程樂(lè)趣的小白,希望自己
 * 能夠在這個(gè)道路上走的很遠(yuǎn)律胀,也希望自己學(xué)習(xí)到的
 * 知識(shí)可以幫助更多的人,分享就是學(xué)習(xí)的一種樂(lè)趣
 * QQ:1069584784
 * csdn:http://blog.csdn.net/wuyinlei
 */

public class UserEntity {

    public String nickName;
    public int userID;
    public String address;
}

接下來(lái)看下Presenter層(要和VIEW和MODEL交互)宋光。在這里我們封裝了Login(登錄的邏輯處理)貌矿,因?yàn)槲覀冞@個(gè)例子是一個(gè)登錄的邏輯判斷,也就是說(shuō)登錄這個(gè)和view和model要打交道罪佳,在這里用到了面向接口編程逛漫,在接口中只有方法,然后在子類(lèi)中取實(shí)現(xiàn)它菇民。

package com.example.mvpdemo.presenter;

/**
 * Created by 若蘭 on 2016/1/13.
 * 一個(gè)懂得了編程樂(lè)趣的小白尽楔,希望自己
 * 能夠在這個(gè)道路上走的很遠(yuǎn),也希望自己學(xué)習(xí)到的
 * 知識(shí)可以幫助更多的人,分享就是學(xué)習(xí)的一種樂(lè)趣
 * QQ:1069584784
 * csdn:http://blog.csdn.net/wuyinlei
 */

public interface PresenterInterface {

    void login();

}

package com.example.mvpdemo.presenter;

import android.text.TextUtils;

import com.example.mvpdemo.model.UserEntity;
import com.example.mvpdemo.view.LoginView;
import com.google.gson.Gson;

/**
 * Created by 若蘭 on 2016/1/13.
 * 一個(gè)懂得了編程樂(lè)趣的小白第练,希望自己
 * 能夠在這個(gè)道路上走的很遠(yuǎn)阔馋,也希望自己學(xué)習(xí)到的
 * 知識(shí)可以幫助更多的人,分享就是學(xué)習(xí)的一種樂(lè)趣
 * QQ:1069584784
 * csdn:http://blog.csdn.net/wuyinlei
 */

public class LoginPresenter implements PresenterInterface {

    /**
     * VIEW層,與Presenter進(jìn)行信息交互
     */
    public LoginView mLoginView;
    public String result;

    public LoginPresenter(LoginView mLoginView) {
        this.mLoginView = mLoginView;
    }

    @Override
    public void login() {
        if (TextUtils.isEmpty(mLoginView.getUerName())) {
            mLoginView.onUserNameError();
            return;
        }
        if (TextUtils.isEmpty(mLoginView.getPwd())) {
            mLoginView.onPwdError();
            return;
        }
        if ("123".equals(mLoginView.getPwd())) {

            result = "{\n" +
                    "    \"nickName\": \"wuyinlei\",\n" +
                    "    \"userID\": \"1212\",\n" +
                    "    \"address\": \"河南周口\"\n" +
                    "}";

            /**
             * Model層娇掏,和Presenter層交互
             */
            UserEntity mUserEntity = new UserEntity();
            mUserEntity = new Gson().fromJson(result, UserEntity.class);
            mLoginView.onSuccess(mUserEntity.address);

        } else {
          mLoginView.onError();
            return;
        }
    }
}

下面我們看下activity中的邏輯吧

package com.example.mvpdemo.activity;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.example.mvpdemo.R;
import com.example.mvpdemo.presenter.LoginPresenter;
import com.example.mvpdemo.view.LoginView;

public class MainActivity extends AppCompatActivity implements LoginView{

    private EditText etUserName;
    private EditText etPassword;
    private Button btn;
    private TextView tvResult;

    private LoginPresenter mPresenter;

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

    /**
     * 初始化布局控件
     */
    private void initialize() {

        etUserName = (EditText) findViewById(R.id.etUserName);
        etPassword = (EditText) findViewById(R.id.etPassword);
        btn = (Button) findViewById(R.id.btn);

        tvResult = (TextView) findViewById(R.id.tvResult);

       /* userName = etUserName.getText().toString();
        pwd = etPassword.getText().toString();*/

        initLogin();
    }

    /**
     * 登錄
     *
     * 在這里我們只有使用了Presenter層和View層的交互呕寝,沒(méi)有了所謂的model和view的交互
     */
    private void initLogin() {
        mPresenter = new LoginPresenter(this);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /*login(etUserName.getText().toString(), etPassword.getText().toString());

                if (!TextUtils.isEmpty(result)) {
                    mUserEntity = new Gson().fromJson(result, UserEntity.class);
                    tvResult.setText(mUserEntity.address.toString());
                }*/

                mPresenter.login();
            }
        });
    }

    @Override
    public String getUerName() {
        return etUserName.getText().toString();
    }

    @Override
    public String getPwd() {
        return etPassword.getText().toString();
    }

    @Override
    public void onUserNameError() {
        etUserName.setError("用戶名不能為空");
    }

    @Override
    public void onPwdError() {
        etPassword.setError("密碼不能為空");
    }

    @Override
    public void onError() {
        Toast.makeText(this, "密碼錯(cuò)誤", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onSuccess(String address) {
        tvResult.setText(address);
    }


   /* *//**
     * 登錄邏輯
     *//*
    private void login(String userName, String pwd) {
        if (TextUtils.isEmpty(userName)) {
            etUserName.setError("用戶名不能為空");
            return;
        }
        if (TextUtils.isEmpty(pwd)) {
            etPassword.setError("密碼不能為空");
            return;
        }
        if ("123".equals(pwd)) {

            result = "{\n" +
                    "    \"nickName\": \"小明\",\n" +
                    "    \"userID\": \"1212\",\n" +
                    "    \"address\": \"河南周口\"\n" +
                    "}";

        } else {
            Toast.makeText(this, "密碼錯(cuò)誤", Toast.LENGTH_SHORT).show();
            return;
        }
    }
*/

}

當(dāng)然了哈,這只是簡(jiǎn)單的實(shí)現(xiàn)了一個(gè)判斷登錄的邏輯婴梧,不過(guò)可以衍生到工作中用到了方面

接下來(lái)我們看下MVC模式和MVP模式的對(duì)比吧下梢。

  • MVC允許 View 和 Model 直接通訊。

  • MVP 中塞蹭,所有對(duì) Model 的交互都由Presenter 內(nèi)部來(lái)完成

  • MVP 中孽江,View 通常會(huì)抽象化出來(lái)一系列的接口。(面向接口編程)(可以看出番电,我們這就五個(gè)類(lèi)岗屏,其中用到了兩個(gè)接口)

  • MVP 相對(duì)于 MVC 而言,大大降低了耦合度(Activity 不再進(jìn)行復(fù)雜的操作)漱办,層級(jí)更明顯这刷,更利于單元測(cè)試。

  • MVP 的缺點(diǎn):類(lèi)文件會(huì)爆炸娩井,有一定的學(xué)習(xí)成本暇屋。(本來(lái)兩個(gè)文件就可以實(shí)現(xiàn)或者一個(gè),也就是model + view + controller都下載activity中洞辣,這里我們使用了五個(gè)類(lèi)實(shí)現(xiàn))
    但是咐刨,MVC和MVP各有各的優(yōu)點(diǎn),這個(gè)還是要看自己抉擇的扬霜。
    下面附上該項(xiàng)目的git地址所宰。https://github.com/wuyinlei/MvpDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市畜挥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌婴谱,老刑警劉巖蟹但,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躯泰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡华糖,警方通過(guò)查閱死者的電腦和手機(jī)麦向,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)客叉,“玉大人诵竭,你說(shuō)我怎么就攤上這事〖娌” “怎么了卵慰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)佛呻。 經(jīng)常有香客問(wèn)我裳朋,道長(zhǎng),這世上最難降的妖魔是什么吓著? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任鲤嫡,我火速辦了婚禮,結(jié)果婚禮上绑莺,老公的妹妹穿的比我還像新娘暖眼。我一直安慰自己,他們只是感情好纺裁,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布诫肠。 她就那樣靜靜地躺著,像睡著了一般对扶。 火紅的嫁衣襯著肌膚如雪区赵。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,713評(píng)論 1 312
  • 那天浪南,我揣著相機(jī)與錄音笼才,去河邊找鬼。 笑死络凿,一個(gè)胖子當(dāng)著我的面吹牛骡送,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播絮记,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼摔踱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了怨愤?” 一聲冷哼從身側(cè)響起派敷,我...
    開(kāi)封第一講書(shū)人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后篮愉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體腐芍,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年试躏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了猪勇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颠蕴,死狀恐怖泣刹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情犀被,我是刑警寧澤椅您,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站弱判,受9級(jí)特大地震影響襟沮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜昌腰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一开伏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遭商,春花似錦固灵、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至祠汇,卻和暖如春仍秤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背可很。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工诗力, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人我抠。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓苇本,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親菜拓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瓣窄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容