细说Android框架设计三剑客MVCMVP和MVVM

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了细说Android框架设计三剑客MVCMVP和MVVM相关的知识,希望对你有一定的参考价值。

    最近几年的移动端开发越来越火,功能越来越强大,处理业务越来越复杂,因此对系统扩展性的要求越来越高。而为了更好地进行移动端架构设计,我们最常用的就是MVC和MVP,今天本篇博客就和大家一起聊一聊这两种框架设计。

MVC框架

MVC的定义

    MVC (Model-View-Controller):M是指逻辑模型,V是指视图模型,C则是控制器。使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式,而C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新,这与《设计模式》中的观察者模式是完全一样。

为何用MVC

  • 从用户的角度出发,用户可以根据自己的需求,选择自己合适的浏览数据的方式。
  • 从开发者的角度,MVC把应用程序的逻辑层与界面是完全分开的,这样,界面设计人员可以直接参与到界面开发,程序员就可以把精力放在逻辑层上。而不是像以前那样,设计人员把所有的材料交给开发人员,由开发人员来实现界面。

MVC的通信方式

技术分享

首先,View发送命令到Controller,然后Controller处理完业务逻辑后让Model改变状态,最后由Model将新的数据发送到View,用户得到数据响应。

android中的MVC

    1.视图层(View):一般采用XML文件进行界面的描述,使用的时候可以非常方便的引入。
    2.控制层(Controller):Android的控制层通常是在Acitvity中实现。
    3.模型层(Model):对数据库的操作、对网络等的操作都应该在Model里面处理,当然对业务计算等操作也是必须放在的该层的。

MVP框架

MVP的定义

    MVC (Model-View-Presenter):MVP其实是由MVC演变而来的,其中的M依然是指逻辑模型,V依然是指视图模型,而P(中间桥梁)则代替了C成为了逻辑控制器的角色。

MVC和MVP到底有啥区别

    区别就在于MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。我们知道在MVC里,View是可以直接访问Model的。从而,View里会包含Model信息,不可避免的还要包括一些业务逻辑。 在MVC模型里,更关注的Model的不变,而同时有多个对Model的不同显示,即View。所以,在MVC模型里,Model不依赖于View,但是View是依赖于Model的。

MVP的通信方式

技术分享

    首先MVP各部分之间的通信都是双向的,但是唯独View与Model之间是不发生联系的,二者之间的通信都是通过Presenter传递的。在MVP里,应用程序的逻辑主要在Presenter来实现,其中的View是很薄的一层。在这个过程中,View是很简单的,能够把信息显示清楚就可以了。在后面,根据需要再随便更改View,而对Presenter没有任何的影响了。 如果要实现的UI比较复杂,而且相关的显示逻辑还跟Model有关系,就可以在View和Presenter之间放置一个Adapter。由这个 Adapter来访问Model和View,避免两者之间的关联。而同时,因为Adapter实现了View的接口,从而可以保证与Presenter之间接口的不变。这样就可以保证View和Presenter之间接口的简洁,又不失去UI的灵活性。 在MVP模式里,View只应该有简单的Set/Get的方法,用户输入和设置界面显示的内容,除此就不应该有更多的内容,绝不容许直接访问Model–这就是与MVC很大的不同之处。

MVP的优缺点

以下内容来自百度百科

1、模型与视图完全分离,我们可以修改视图而不影响模型
2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部
3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。
4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)

由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。还有一点需要明白,如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了。

MVVM框架

MVVM的定义

    MVC (Model-View-ViewModel):MVVM和MVP的区别其实不大,只不过是把presenter层换成了ViewModel层,再有就是View层和ViewModel层是相互绑定的关系,当我们更新ViewModel层的数据的时候,View层会相应的更新UI。

MVVM的通信方式

技术分享
    MVVM它采用的是数据绑定(data-binding)方式,而且是双向绑定:View绑定到ViewModel,然后执行一些命令在向它请求一个动作。而反过来,ViewModel跟Model通讯,告诉它更新来响应UI。

MVVM优点

以下内容来自百度百科
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点

  1. 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的”View”上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
  2. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
  3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
  4. 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。

MVP案例实践

    下面我选取MVP框架作为主要案例,给大家讲解如何将一个普通项目改造成MVP框架模式的项目,帮助大家理解MVP框架模式的意义。

传统项目结构

技术分享

该项目中我们定义了一个学生信息列表,用来显示图片和文字;项目源码如下:

MainActivity.java

/**
 * 功能:MainActivity 用列表形式实现
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016/10/01
 */
public class MainActivity extends AppCompatActivity implements IStudentView{

    ListView myList;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myList = (ListView) findViewById(R.id.my_list);
        myList.setAdapter(new StudentAdapter(MainActivity.this));
    }
}

StudentAdapter.java

/**
 * 功能:StudentAdapter
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016/10/01
 */
public class StudentAdapter extends BaseAdapter {

    private LayoutInflater myInflater;
    private List<Student> data;

    public StudentAdapter(Context context, List<Student> data) {
        myInflater = LayoutInflater.from(context);
        this.data = data;
    }

    @Override
    public int getCount() {
        return DataUtils.stuSize();
    }

    @Override
    public Object getItem(int position) {
        return DataUtils.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View stuView = myInflater.inflate(R.layout.stu_item, null);
        Student student = DataUtils.get(position);
        ImageView imgStu = (ImageView) stuView.findViewById(R.id.img_stu);
        imgStu.setImageResource(student.getStuImg());
        TextView tvStu = (TextView) stuView.findViewById(R.id.tv_name);
        tvStu.setText(student.getName());
        return stuView;
    }

}

DataUtils.java

/**
 * 功能:初始化数据工具类
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016/10/01
 */
public class DataUtils {

    public final static List<Student> stuData = new ArrayList<Student>();

    public static Student get(int i){
        return stuData.get(i);
    }

    public static int stuSize(){
        return stuData.size();
    }

    static {
        stuData.add(new Student("张三",R.mipmap.ic_launcher));
        stuData.add(new Student("李四",R.mipmap.ic_launcher));
        stuData.add(new Student("王五",R.mipmap.ic_launcher));
        stuData.add(new Student("赵六",R.mipmap.ic_launcher));
        stuData.add(new Student("陈七",R.mipmap.ic_launcher));
        stuData.add(new Student("孙八",R.mipmap.ic_launcher));
        stuData.add(new Student("猴子搬来的救兵",R.mipmap.ic_launcher));

    }
}

Student.java

/**
 * 功能:学生Bean
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016/10/01
 */
public class Student {

    private String name;
    private int stuImg;

    public Student() {
    }

    public Student(String name, int stuImg) {
        this.name = name;
        this.stuImg = stuImg;
    }

    public String getName() {
        return name;
    }

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

    public int getStuImg() {
        return stuImg;
    }

    public void setStuImg(int stuImg) {
        this.stuImg = stuImg;
    }

}

项目运行结果:

技术分享

MVP改造结构

技术分享

改造后的项目,我们增加了三大模块,分别是:

  • Model包负责处理数据
  • View包负责显示处理
  • Presenter包是中间桥梁,负责Model和View的交互

通过MVP框架实现数据适配

定义Model层接口

public interface IStudentModel {
    // 加载数据
    void loadStudent(StudentOnLoadListener listener);
    interface StudentOnLoadListener{
       void onComplete(List<Student> students);
    }
}

定义View层接口

public interface IStudentView {
    // 显示进度
    void showLoading();
    // 显示学生
    void showStudents(List<Student> students);
}

添加Model层

/**
 * 功能:StudentModel 第一次数据处理
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016/10/13 0013
 */
public class StudentModelImplOne implements IStudentModel {
    @Override
    public void loadStudent(StudentOnLoadListener listener) {
        Log.i("castiel","执行了StudentModelImplOne数据加载");
        //模拟Json数据
        List<Student> jsonStu1 = new ArrayList<Student>();
        jsonStu1.add(new Student("张三11", R.mipmap.ic_launcher));
        jsonStu1.add(new Student("李四11", R.mipmap.ic_launcher));
        jsonStu1.add(new Student("王五11", R.mipmap.ic_launcher));
        jsonStu1.add(new Student("赵六11", R.mipmap.ic_launcher));
        jsonStu1.add(new Student("陈七11", R.mipmap.ic_launcher));
        jsonStu1.add(new Student("孙八11", R.mipmap.ic_launcher));
        jsonStu1.add(new Student("猴子搬来的救兵11http://blog.csdn.net/mynameishuangshuai", R.mipmap.ic_launcher));
        // 通过回调方式传递数据
        if (listener != null) {
            listener.onComplete(jsonStu1);
        }
    }
}

添加Presenter层

public class StudentPresenterOne {
    // Model
   IStudentModel mStudentModel = new StudentModelImplOne();
    // View
    IStudentView mStudentView;
    // 初始化View
    public StudentPresenterOne(IStudentView mStudentView) {
        this.mStudentView = mStudentView;
    }
    public void fetch(){
        mStudentView.showLoading();// 显示进度
        // Model获取数据
        if (mStudentModel != null){
            mStudentModel.loadStudent(new IStudentModel.StudentOnLoadListener() {
                @Override
                public void onComplete(List<Student> students) {
                    // 得到数据后给View显示数据
                    mStudentView.showStudents(students);
                }
            });
        }
    }
}

执行操作

/**
 * 功能:MainActivity 用列表形式实现
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016/10/01
 */
public class MainActivity extends AppCompatActivity implements IStudentView{

    ListView myList;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myList = (ListView) findViewById(R.id.my_list);
        new StudentPresenterOne(this).fetch();
    }

    @Override
    public void showLoading() {
        Toast.makeText(this, "正在加载数据中……", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showStudents(List<Student> students) {
        // 负责显示
        myList.setAdapter(new StudentAdapter(MainActivity.this,students));
    }
}

显示结果

技术分享

通过MVP框架实现View改变

实现新的Model层

public class StudentModelImplTwo implements IStudentModel {
    @Override
    public void loadStudent(StudentOnLoadListener listener) {

        Log.i("castiel","执行了StudentModelImplTwo数据加载");
        // 模拟网络加载延时数据
        SystemClock.sleep(1000);

        //模拟Json数据
        List<Student> jsonStu2 = new ArrayList<Student>();
        jsonStu2.add(new Student("张三22", R.mipmap.ic_launcher));
        jsonStu2.add(new Student("李四22", R.mipmap.ic_launcher));
        jsonStu2.add(new Student("王五22", R.mipmap.ic_launcher));
        jsonStu2.add(new Student("赵六22", R.mipmap.ic_launcher));
        jsonStu2.add(new Student("陈七22", R.mipmap.ic_launcher));
        jsonStu2.add(new Student("孙八22", R.mipmap.ic_launcher));
        jsonStu2.add(new Student("猴子搬来的救兵22http://blog.csdn.net/mynameishuangshuai", R.mipmap.ic_launcher));

        // 通过回调方式传递数据
        if (listener != null) {
            listener.onComplete(jsonStu2);
        }
    }
}

实现新的Presenter层

public class StudentPresenterTwo {
    // Model
    IStudentModel mStudentModel = new StudentModelImplTwo();
    // View
    IStudentView mStudentView;
    // 初始化View
    public StudentPresenterTwo(IStudentView mStudentView) {
        this.mStudentView = mStudentView;
    }

    public void fetch(){
        mStudentView.showLoading();// 显示进度
        // Model获取数据
        if (mStudentModel != null){
            mStudentModel.loadStudent(new IStudentModel.StudentOnLoadListener() {
                @Override
                public void onComplete(List<Student> students) {
                    // 得到数据后给View显示数据
                    mStudentView.showStudents(students);
                }
            });
        }
    }
}

添加新的Activity,改变列表布局为网格布局

/**
 * 功能:TwoActivity 用网格形式实现
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016/10/01
 */
public class TwoActivity extends AppCompatActivity implements IStudentView{

    GridView myGrid;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_two);
        myGrid = (GridView) findViewById(R.id.my_grid);
        new StudentPresenterTwo(this).fetch();
    }

    @Override
    public void showLoading() {
        Toast.makeText(this, "正在加载数据中……", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showStudents(List<Student> students) {
        // 负责显示
        myGrid.setAdapter(new StudentGridAdapter(TwoActivity.this,students));
    }
}

新的Adapter

public class StudentGridAdapter extends BaseAdapter {

    private LayoutInflater myInflater;
    private List<Student> data;

    public StudentGridAdapter(Context context, List<Student> data) {
        myInflater = LayoutInflater.from(context);
        this.data = data;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View stuView = myInflater.inflate(R.layout.stu_item, null);
        Student student = data.get(position);
        ImageView imgStu = (ImageView) stuView.findViewById(R.id.img_stu);
        imgStu.setImageResource(student.getStuImg());
        TextView tvStu = (TextView) stuView.findViewById(R.id.tv_name);
        tvStu.setText(student.getName());
        return stuView;
    }

}

执行结果

技术分享








以上是关于细说Android框架设计三剑客MVCMVP和MVVM的主要内容,如果未能解决你的问题,请参考以下文章

Android:安卓学习笔记之MVCMVP模式的简单理解和使用

Android 对比MVCMVP来聊聊MVVM模式的理解

Android -- 每日一问:谈谈MVCMVP和MVVM模式,你有在自己的项目中使用过吗?

Android MVCMVP和MVVP的概念运用及区别

如何实现自己的Android MVP框架

对比MVCMVP来聊聊MVVM模式的理解