最近几年的移动端开发越来越火,功能越来越强大,处理业务越来越复杂,因此对系统扩展性的要求越来越高。而为了更好地进行移动端架构设计,我们最常用的就是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),有几大优点
- 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的”View”上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
- 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
- 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
- 可测试。界面素来是比较难于测试的,而现在测试可以针对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;
}
}
执行结果