实战 | 详解MVC、MVP模式并分别实现登录界面案例

参考资料(《(菜鸟窝)安卓进阶必学》)

本文参考技术资料做一个笔记,主要内容是总结MVC、MVP两个设计模式的思想,以及分别运用这两个模式的实现,实现两个project(MVCSmallTest还有MVPTest),内容都是登录界面。

文章主要内容摘要:

  • MVC模式的分析和实战
  • MVP模式的分析和实战
  • MVP模式下多个Activity情况下的接口抽取

实战案例效果如下:输入正确的密码并点击登录按钮时,Toast“登录成功”,若密码或账号错误,则Toast“登录失败”,若全部输入,则Toast“用户名和密码不能为空”:

MVC模式

View层其实就是程序的UI界面,用于向用户展示数据以及接收用户的输入(比如EditText.getText().toString()); 而Model层就是JavaBean实体类,用于保存实例数据; Controller控制器用于更新UI界面和数据实例

特点:
  • 使用多
  • 软件开发最早使用的设计模式
  • xml做View层,Activity做C层
弊端:Activity既是V又是C,UI逻辑和业务逻辑都写在一块(Activity.java 中),没有实现V和C的分离;
下面用MVC模式编写本例子

MainActivity.java:

package com.lwp.mvcsmalltest;

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

public class MainActivity extends AppCompatActivity {

    private EditText et_username;
    private EditText et_pwd;
    private Button btn_login;

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

        et_username = (EditText)findViewById(R.id.et_username);
        et_pwd = (EditText)findViewById(R.id.et_pwd);
        btn_login = (Button) findViewById(R.id.btn_login);

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String userName = et_username.getText().toString();
                String pwd = et_pwd.getText().toString();
                if(!TextUtils.isEmpty(userName) && !TextUtils.isEmpty(pwd)){
                    login(userName,pwd);
                }else{
                    Toast.makeText(MainActivity.this,"用户名和密码不能为空",Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    private void login(final String userName, final String pwd) {
        //开一个子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(2000);//沉睡两秒模拟登录
                //runOnUiThread切回主线程更新UI
                if(userName.equals("jiangxue") && pwd.equals("666666")){
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this,"登录成功",Toast.LENGTH_SHORT).show();
                        }
                    });
                }else{
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this,"登录失败",Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }
        }).start();
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/et_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="用户名"/>

    <EditText
        android:id="@+id/et_pwd" 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="密码"/>

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="登录"/>

</LinearLayout>

刚刚说过View层其实就是程序的UI界面,用于向用户展示数据以及接收用户的输入; Controller控制器用于更新UI界面和数据实例, 由此我们可以分析MainActivity.java得到以下的结果:MainActivity既是V又是C,没有实现V和C的分离;业务逻辑(本例中即login()跟UI逻辑都写在一个Activity里面),这样写毫无疑问很冗杂,对于简单的项目也许没什么影响和明显的弊端,甚至显得方便,但是一旦项目大了,这样写会使可读性非常低,不利于项目后期的诸多工作;

到此,我们便用MVC模式完成了登录界面小案例; 下面分析MVP模式

MVP模式

presenter   n.节目主持人,演播员; 推荐者; 提出者; 赠送者

MVP模式的核心思想:

对比与MVC,把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model类。

作用:
  1. 分离视图逻辑和业务逻辑,降低耦合;
  2. Activity只处理生命周期的任务,代码简洁;
  3. 视图逻辑和业务逻辑抽象到了View和Presenter中,提高阅读性;
  4. Presenter被抽象成接口,可以有多种具体的实现
  5. 业务逻辑在Presenter中,避免后台线程引用Activity导致内存泄漏

下面从零到一开始实战:

首先新建一个项目,在主包下创建三个包(即model、presenter、view),待会儿用于存放MVP三层各自的代码。然后把MainActivity.java拉进view包:

接下来我们把方才的几个UI逻辑都抽象成View接口,方才哪几个UI逻辑呢?就登陆成功、登录失败、弹出toast等这些个UI逻辑了:

把UI逻辑抽象成BaseView接口:
package com.lwp.mvptest.view;

/**
 * Created by 700 on 2019/1/12.
 */

public interface BaseView {
    void showToast(String msg) ;
    void loginSuccess(String msg) ;
    void loginFailed(String msg) ;
}
把业务逻辑抽象成Presenter接口:

presenter/BasePresenter.java:

package com.lwp.mvptest.presenter;

import android.provider.UserDictionary;

import com.lwp.mvptest.model.User;
import com.lwp.mvptest.view.BaseView;

/**
 * Created by 700 on 2019/1/12.
 */

public interface BasePresenter {

    void attachView(BaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();

    void login(User user);
}

model/User.java:

package com.lwp.mvptest.model;

/**
 * Created by 700 on 2019/1/12.
 */

public class User {

    private String name;
    private String pwd;

    public User(String name, String pwd){
        this.name = name;
        this.pwd = pwd;
    }

    public String getName(){return name;}

    public void setName(String name) {this.name = name;}

    public String getPwd() {return pwd;}

    public void setPwd(String pwd) {this.pwd = pwd;}

}

此时目录:

编写MainActivity.java: 实例化各组件,实例化model类对象,实现UI逻辑接口:

public class MainActivity extends AppCompatActivity implements BaseView{

    private EditText et_username;
    private EditText et_pwd;
    private Button btn_login;

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

        et_username = (EditText)findViewById(R.id.et_username);
        et_pwd = (EditText)findViewById(R.id.et_pwd);
        btn_login = (Button) findViewById(R.id.btn_login);

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //new的时候把东西get下来,赋给model Class ,完美!
                User user = new User(et_username.getText().toString(),et_pwd.getText().toString());
            }
        });
    }

    @Override
    public void showToast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loginSuccess(String msg) {
        showToast(msg);
    }

    @Override
    public void loginFailed(String msg) {
        showToast(msg);
    }
}

presenter包下再创建一个 MainPresenter(Implement) 类,书写业务逻辑,实现业务逻辑接口:

这里, 我们在presenter包下写好 抽象的业务逻辑接口BasePresenter ,   在接口里面定义好抽象的业务逻辑方法, 然后在presenter包下又写下一个对应 抽象的业务逻辑接口BasePresenter业务逻辑实现类MainPresenter,   用于实现对应的接口; 这样子,我们便把业务逻辑抽象出来,实现在业务逻辑实现类中, 到时候Activity.java中要使用对应的业务逻辑的时候, 只需要简简单单实例化一个对应的业务逻辑实现类对象, 用它调用一个自定义方法(如下面的attachView()), 把Activity的this指针(也即activity本身)赋给业务逻辑实现类对象中的全局变量, 之后即可用这个业务逻辑类对象去调用实现类中对应的业务逻辑方法, 接收对应的数据,实现对应的业务逻辑; 也就是, 现在activity要使用业务逻辑的话就不用再在写具体的业务逻辑了, 抽象地说,可以说只要三行代码; 第一行实例化业务逻辑实现类的对象, 第二行绑定this和业务逻辑实现类的对象, 第三行使用对象并以相关数据为参数调用相关的业务逻辑方法实现即可;

package com.lwp.mvptest.presenter;

import android.text.TextUtils;

import com.lwp.mvptest.model.User;
import com.lwp.mvptest.view.BaseView;

/**
 * Created by 700 on 2019/1/12.
 */

public class MainPresenter implements BasePresenter {

    private BaseView baseView;

    @Override
    public void attachView(BaseView v) {
        this.baseView = v;//绑定的时候把view置进来
    }

    @Override
    public void detachView() {
        baseView = null;//解绑时置空即可
    }

    @Override
    public void login(User user) {
        if(!TextUtils.isEmpty(user.getName()) && !TextUtils.isEmpty(user.getPwd())) {
            if (user.getName().equals("jiangxue") && user.getPwd().equals("666666")) {
                baseView.loginSuccess("登陆成功");
            }else {
                baseView.loginFailed("登录失败");
            }
        }else{
            baseView.showToast("用户名或密码不能为空");
        }
    }
}

接下来修改MainActivity,实例化业务逻辑类对象,绑定View(以及解绑逻辑),传入数据供给并调用业务逻辑方法:

package com.lwp.mvptest.view;

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

import com.lwp.mvptest.R;
import com.lwp.mvptest.model.User;
import com.lwp.mvptest.presenter.MainPresenter;

public class MainActivity extends AppCompatActivity implements BaseView{

    private EditText et_username;
    private EditText et_pwd;
    private Button btn_login;

    private MainPresenter mainPresenter;//声明业务逻辑类

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

        et_username = (EditText)findViewById(R.id.et_username);
        et_pwd = (EditText)findViewById(R.id.et_pwd);
        btn_login = (Button) findViewById(R.id.btn_login);

        mainPresenter = new MainPresenter();//实例化业务逻辑类对象
        mainPresenter.attachView(this);//绑定view(把this付给业务逻辑类中的全局变量,
                                        //业务逻辑类中的逻辑方法会使用到这个全局变量(传进去的this),
                                        // 从而具体实现业务逻辑类中的业务逻辑)

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //new的时候把东西get下来,赋给model Class ,完美!
                User user = new User(et_username.getText().toString(),et_pwd.getText().toString());
                mainPresenter.login(user);//传入数据,调用业务逻辑方法
            }
        });
    }

    @Override
    public void showToast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loginSuccess(String msg) {
        showToast(msg);
    }

    @Override
    public void loginFailed(String msg) {
        showToast(msg);
    }

    //销毁状态周期时解除绑定
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mainPresenter.detachView();
    }
}

成功运行:

此时目录如下:

小结: User 用于存储数据; BasePresenter是业务逻辑接口抽象; MainPresenter实现业务逻辑接口; BaseView是抽象的UI逻辑接口,在MainActivity中实现; MainActivity统筹所有;

至此我们其实便用MVP模式完成了登录界面小案例;

MVP模式下多个Activity情况下的接口抽取

假如我们项目中再来一个OtherActivity,实现功能如此如此(见下方代码注释):

package com.lwp.mvptest.view;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.lwp.mvptest.R;

public class OtherActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);
        
        //业务逻辑相关        请求图片或下载上传。。。
        //UI逻辑          显示图片        显示进度条   弹Toast。。。
    }
}

我们按照上面的思路给它抽象出对应的逻辑接口和逻辑实现类:

UI逻辑接口OtherBaseView:

package com.lwp.mvptest.view;

/**
 * Created by 700 on 2019/1/13.
 */

public interface OtherBaseView {
    void showToast(String msg);
    void showProgress(int progress);
}

业务逻辑接口OtherPresenter:

package com.lwp.mvptest.presenter;

import com.lwp.mvptest.view.OtherBaseView;

/**
 * Created by 700 on 2019/1/13.
 */

public interface OtherPresenter {
    void attachView(OtherBaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();
    
    void uploadImage(String path);
}

业务逻辑接口实现类OtherPresenterImpl:

package com.lwp.mvptest.presenter;

import com.lwp.mvptest.view.OtherBaseView;

/**
 * Created by 700 on 2019/1/13.
 */

public class OtherPresenterImpl implements OtherPresenter {
    @Override
    public void attachView(OtherBaseView v) {
        
    }

    @Override
    public void detachView() {

    }

    @Override
    public void uploadImage(String path) {

    }
}

OtherActivity,实现OtherBaseView:

package com.lwp.mvptest.view;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.lwp.mvptest.R;

public class OtherActivity extends AppCompatActivity implements OtherBaseView{

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

        //业务逻辑相关        请求图片或下载上传。。。
        //UI逻辑          显示图片        显示进度条   弹Toast。。。
    }

    @Override
    public void showToast(String msg) {
        
    }

    @Override
    public void showProgress(int progress) {

    }
}

此时项目结构:

UI逻辑接口抽取

我们观察一下,OtherBaseView和BaseView有什么不同,

public interface BaseView {
    void showToast(String msg) ;
    void loginSuccess(String msg) ;
    void loginFailed(String msg) ;
}
--------------------------------------
public interface OtherBaseView {
    void showToast(String msg);
    void showProgress(int progress);
}

可以发现统一地都有一个showToast()方法(通过这个案例,我们可以体会开发过程中的一些相应的场景——两个抽象接口具有相同的方法时候),

这样的话,我们可以对这两个接口进行抽取 (抽取像我们数学表达式中的提公因式,是普适而重要的一环),

下面在View包下新建一个MainBaseView,代替原来BaseView的位置(MainActivity的UI逻辑接口), 抽取相同的部分放在BaseView中,MainBaseView和OtherBaseView继承BaseView:

public interface BaseView {
    void showToast(String msg) ;
}
------------------
package com.lwp.mvptest.view;

/**
 * 只负责MainActivity的UI逻辑
 * Created by 700 on 2019/1/13.
 */

public interface MainBaseView extends BaseView{
    void loginSuccess(String msg) ;
    void loginFailed(String msg) ;
}

------------------
package com.lwp.mvptest.view;

/**
 * 只负责OtherActivity的UI逻辑
 * Created by 700 on 2019/1/13.
 */

public interface OtherBaseView extends BaseView{
    void showProgress(int progress);
}

最后记得public class MainActivity extends AppCompatActivity implements MainBaseView

业务逻辑接口的抽取

我们观察一下BasePresenter和OtherPresenter的区别:

public interface BasePresenter {

    void attachView(BaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();

    void login(User user);
}
------------------
public interface OtherPresenter {

    void attachView(OtherBaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();

    void uploadImage(String path);
}

我们可以将attachView()以及detachView()抽取出来,放在BasePresenter中,新建一个MainBasePresenter,让其和OtherPresenter 继承自BasePresenter:

public interface BasePresenter<T> {

    void attachView(T v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();
}
--------------------------------------
/**
 * 只为MainActivity提供业务逻辑
 * Created by 700 on 2019/1/13.
 */

public interface MainBasePresenter extends BasePresenter<MainBaseView>{
    void login(User user);
}
--------------------------------------
/**
 * 只为OtherActivity提供业务逻辑
 * Created by 700 on 2019/1/13.
 */

public interface OtherPresenter extends BasePresenter<OtherBaseView>{
    void uploadImage(String path);
}

注意这里的BasePresenter要用泛型进行定义,因为下面的子业务逻辑模块(如MainBasePresenter )都需要用attachView()绑定对应的UI逻辑接口(如MainBaseView),所以这里使用泛型,子逻辑模块在继承时可以动态匹配。

接下来进入MainPresenter,进行代码的修改,

删掉下面这一段,然后更改implement到MainBasePresenter,然后Alt+Enter自动生成对应方法,最后修改类中对应的内容:

package com.lwp.mvptest.presenter;

import android.text.TextUtils;

import com.lwp.mvptest.model.User;
import com.lwp.mvptest.view.BaseView;
import com.lwp.mvptest.view.MainBaseView;

/**
 *MainActivity业务逻辑的具体实现
 * Created by 700 on 2019/1/12.
 */

public class MainPresenter implements MainBasePresenter {

    private MainBaseView mainBaseView;

    @Override
    public void attachView(MainBaseView v) {
        this.mainBaseView = v;
    }

    @Override
    public void detachView() {
        mainBaseView = null;//解绑时置空即可
    }

    @Override
    public void login(User user) {
        if(!TextUtils.isEmpty(user.getName()) && !TextUtils.isEmpty(user.getPwd())) {
            if (user.getName().equals("jiangxue") && user.getPwd().equals("666666")) {
                mainBaseView.loginSuccess("登陆成功");
            }else {
                mainBaseView.loginFailed("登录失败");
            }
        }else{
            mainBaseView.showToast("用户名或密码不能为空");
        }
    }
}

最后为各个文件添加注释...

/**
 * OtherActivity业务逻辑的具体实现
 * Created by 700 on 2019/1/13.
 */

public class OtherPresenterImpl implements OtherPresenter...

到此,逻辑编写便完毕了,这里的Other系列都是作为演绎作用的空模板,暂时没有实现具体的功能,

最后成功运行:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券