一,前言:
关于Android程序的构架, 目前基本上可以分为三类,即MVC、MVP、MVVM。当前MVP和MVVM的使用相对比较广泛,当然MVC也并没有过时之说。它们各有各的特点,具体如何使用要结合实际项目中来确定,架构模式本身没有好坏之分,我们只有了解了这些架构设计的特点之后,才能在进行开发的时候选择适合自己项目的架构模式,这也是本文的目的。
二,MVC、MVP、MVVM对比
1、MVC
MVC (Model-View-Controller, 模型-视图-控制器),它是一种软件的设计典范,它通过“业务逻辑、数据、界面显示”的分离手段来组织代码。MVC框架模式最早由Trygve Reenskaug 于1978年在Smalltalk-80系统上首次提出,对于MVC,Reenskaug也给出了自己的介绍:
Model: Model可以是一个独立的对象,也可以是一系列对象的集合体;
View: View是Model中一些重要数据在视觉上的体现;
Controller: Controller用于连接User和System,比如当Controller接受到用户的输出时,会将其转换成合适的事件消息,并将该事件消息传递给一个或多个View;
MVC作为最早且应用最广泛的架构模式,其并没有一个明确的定义,网上流传的 MVC 架构图也是形态各异,个人认为比较经典的MVC结构如下图:
模型层(Model):
程序需要操作的数据或信息(系统中的业务逻辑部分),比如数据的存储、网络请求等,同时Model与View也存在一定的耦合,通过某种事件机制(如观察者模式)通知View状态改变或更新;Model还会接收来自Controller的事件,Model也会允许View 查询相关数据以显示自身状态.
视图层(View):
直接面向于最终用户的视图层,并提供给用户操作界面,是Model的具体表现形式,是程序的外壳。该层只负责展示数据,主要是由各种GUI组件组成,同时响应用户的交互行为并触发Controller的逻辑,View还会通过在Model中注册事件监听Model的改变以此来刷新自身并展示给用户(如监听Model中的Bitmap图像,当Bitmap加载或清除时,对应的View显示不同的图像).
控制器层(Controller):
Controller由View根据用户行为触发,并响应来自View的交互,然后根据View的事件逻辑修改对应的Model,Controller并不关心View如何展示数据或状态,而是通过修改Model并由Model的事件机制来触发View的刷新.
MVC优缺点及适用场景
Activity并非标准的Controller,它一方面用来控制了布局,另一方面还要在Activity中写业务代码,造成了Activity既像View又像Controller。
在Android开发中,就是指直接使用Activity并在其中写业务逻辑的开发方式。显然,一方面Activity本身就是一个视图,另一方面又要负责处理业务逻辑,因此逻辑会比较混乱。
1.1 优点:
1、耦合性低,MVC本质是分层解耦,将表现层与表现逻辑很好的分离,减少模块代码之间的相互影响.
2、可扩展性好。由于耦合性低,添加需求,扩展代码就可以减少修改之前的代码,降低bug的出现率.
3、模块职责划分明确。主要划分层M,V,C三个模块,利于代码的维护.
1.2 缺点:
View层和Model层是相互可知的,这意味着两层之间存在耦合;
在Android开发中,xml作为View层,控制能力实在太弱了,只能把代码写在Activity中,造成了Activity既是Controller层,又是view层这样一个窘境,而且Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户界面,并接受和处理来自用户的操作请求,进而作出响应。随着应用内界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大而臃肿。
1.3 适用场景:
适用于功能较少、业务逻辑简单、界面不复杂的小型项目。
2、MVP
MVP全称Model View Presenter,它是MVC的一个演化版本,是目前APP采用的主流架构设计模式,常用MVP结构如下图:
MVP几个主要部分如下:
模型层 (Model):主要提供数据存取功能。Presenter需要通过Model层存取、获取数据,Model就像一个数据仓库,更直白说Model是封装了数据、网络获取数据的角色,或者是两种数据获取方式的集合。
视图层 (View):处理用户事件和视图。在Android中,可能是指Activity、Fragment或者View。它含有一个Presenter成员变量。通常View需要实现一个逻辑接口,将View上的操作转交给Presenter进行实现,最后,Presenter将调用View逻辑接口将结果返回给View元素。
协调者 (Presenter):负责通过Model存取书数据,连接View和Model,从Model中取出数据交给View。Presenter作为View和Model的桥梁,它从Model层检索数据后,返回给View,使得View和Model之间没有耦合,也将业务逻辑从View层上抽离出来。
所以,对于MVP的架构设计,我们有以下几点需要说明:
这里的Model是用来存取数据的,也就是用来从指定的数据源中获取数据,不要将其理解成MVC中的Model。在MVC中Model是数据模型,在MVP中,我们用Bean来表示数据模型。
Model和View不会直接发生关系,它们需要通过Presenter来进行交互。在实际的开发中,我们可以用接口来定义一些规范,然后让我们的View和Model实现它们,并借助Presenter进行交互即可。
2.1 优点:
1、复杂的逻辑处理放在presenter进行处理,减少了activity和fragment的臃肿;
2、M层与V层完全分离,修改V层不会影响M层,降低了耦合性;
3、P层与V层的交互是通过接口来进行的,便于单元测试。
2.2 缺点:
1、由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁,视图需要改变,一般presenter也需要跟着改变;
2、随着逻辑处理的增加,Presenter也会随之变得越来越复杂,代码臃肿的问题其实还是会出现;
3、由于MVP通过接口进行交互的,接口粒度不好设计和控制。粒度太小,就会存在大量接口,粒度太大,解耦效果不好;
2.3适用场景:
适用于界面复杂、复用性高、功能中等、业务逻辑中等或是简单的项目。实际上MVP的思想很适合用于复杂的界面上,我们完全可以在项目中某一部分上使用MVP的思想去灵活实现
3、MVVM
MVVM全称Model View ViewModel,你可以把MVP看做是MVVM的一个改进版本,常用的MVVM结构如下图:
Model:
Model主要是封装数据存储或操作的一些逻辑,还会提供一系列的实体类用于UI绑定,ViewModel 则会在修改这些数据后将数据改变告诉View层并使UI更新。
View:
View用于处理界面的逻辑且不参与业务逻辑相关的操作,只负责显示由ViewModel提供的数据,View层不做任何业务逻辑、不涉及操作数据、不处理数据,UI和数据严格的分开,对应于Activity和XML。
ViewModel:
ViewModel层做的事情刚好和View层相反,ViewModel只做和业务逻辑和业务数据相关的事,不做任何和UI相关的事情,ViewModel层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。ViewModel就是专注于业务的逻辑处理,做的事情也都只是对数据的操作(这些数据绑定在相应的控件上会自动去更改UI)。同时DataBinding框架已经支持双向绑定,让我们可以通过双向绑定获取View层反馈给ViewModel层的数据,并对这些数据进行操作。
MVVM是通过数据来驱动界面的改变的,传统的数据更新操作是通过持有View层的控件,当Modle层数据发生改变时,主动去更新View层的页面,而这种架构则通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层,数据 成为主导因素。这样MVVM层在业务逻辑处理中只要关心数据,不需要直接和UI打交道,在业务处理过程中简单方便很多。ViewModel不涉及任何和UI相关的事,也不持有UI控件的引用。即便是控件改变了(比如:TextView换成EditText),ViewModel也几乎不需要更改任何代码。所以非常完美的解耦了View层和ViewModel,解决了上面我们所说的MVP的痛点。
3.1 优点:
1,低耦合,数据和业务逻辑处于一个独立的ViewModel中,ViewModel只需要关注数据和业务逻辑,不需要和View层打交道。
2,可重用性,你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
3,可分开独立开发,MVVM的分工是非常明显的,由于View和ViewModel之间是松散耦合的:一个是处理业务和数据、一个是专门的UI处理。所以,完全可以由两个人分工来做,一个做UI(XML和Activity)一个写ViewModel,效率更高。
4,由于各层分工明确,极便于单元测试;
5,相对于MVP而言,MVVM不需要我们手动的处理大量的View和Model相关操作,也非常完美的解耦了View层和ViewModel;
3.2缺点:
1,数据绑定使得 Bug 很难被调试,你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
2,对于过大的项目,数据绑定需要花费更多的内存,而对与过于简单的界面,使用MVVM无异是杀鸡用牛刀,某种意义上来说,,数据绑定使得 MVVM 变得复杂和难用了。
3.3适用场景:
由于我本人也没有在实际项目中使用过MVVM,所以具体哪种场景下使用更合适,我也不是很清楚。但是MVVM是不适用于简单的界面和极度复杂的界面,在界面简单的情况下,MVVM反而将我们的逻辑复杂化了,而界面元素过多,相对应的ViewModel的构建和维护成本就会变的很高,不利于项目的发展。
三,实际应用举例(MVP)
由于MVC大家使用的比较多,且相对比较简单,MVVM相对一般项目来说,使用场景不是很频繁,并且本人也没有在实际项目中使用过,MVP对大部分的项目来说,应用场景比较广泛,并且使用也比较频繁,所以接下来,我就以MVP的架构为例,简单说下,如何来构建。主要讲下我实现的思路,下面以一个简单的获取天气的实例来讲解。
步骤一:先根据业务要求,思考界面主要有哪些实现及展示效果,从而设计view层的接口
/**
* 定义界面的一些行为状态
* Created by Administrator on 2018/4/2.
*/
public interface IgetWeatherView {
void showLoading();
void hideLoading();
void getWeatherFialed();//获取天气失败
void getWeatherSuccess(WeatherBean.WeatherInfo weatherInfo);//获取天气成功
void displayWeatherInfo(WeatherBean.WeatherInfo weatherInfo);//展示天气结果
}
步骤二:根据需要的获取的数据,定义数据层的基本接口
public interface IWeahter {
void getWeatherInfo(Context mContext,String url, WeatherListener weatherListener);
/**
* 获取天气结果接口回调
*/
interface WeatherListener{
void getSuccess(WeatherBean.WeatherInfo weatherBean);
void getFailed();
}
}
步骤三:设置数据获取的实现类(真实获取数据的地方)
public class WeatherImpl implements IWeahter{
@Override
public void getWeatherInfo(Context mContext, String url, final WeatherListener weatherListener) {
VolleyRequestUtil.requestGet(mContext, url, "weather", WeatherBean.class, new VolleyListenerInterface(mContext,
VolleyListenerInterface.mListener,VolleyListenerInterface.mErrorListener) {
@Override
public void onSuccess(Object result) {
WeatherBean weatherBean = (WeatherBean) result;
weatherListener.getSuccess(weatherBean.weatherinfo);
}
@Override
public void onError(VolleyError error) {
weatherListener.getFailed();
}
});
}
}
步骤四:设计提供逻辑交互的presenter层,该层必须持有view和model层的引用,然后在逻辑交互过程中把两者进行关联。
public class WeatherPresenter {
private IWeahter iweather;
private IgetWeatherView getWeatherView;
public WeatherPresenter (IgetWeatherView getWeatherView){
this.getWeatherView= getWeatherView;
iweather= new WeatherImpl();
}
/**
* 获取天气信息
* @param context
*/
public void getWeatherInfo(Context context){
getWeatherView.showLoading();
iweather.getWeatherInfo(context, "http://www.weather.com.cn/data/sk/101010100.html", new IgetWeatherView .WeatherListener() {
@Override
public void getSuccess(WeatherBean.WeatherInfo weatherInfo) {
getWeatherView.hideLoading();
getWeatherView.getWeatherSuccess(weatherInfo);
getWeatherView.displayWeatherInfo(weatherInfo);
}
@Override
public void getFailed() {
getWeatherView.hideLoading();
getWeatherView.getWeatherFialed();
}
});
}
步骤五,view层的实现(Activity)。有了presenter,activity的具体实现只要实现view层的基本接口即可,剩下的只是利用presenter做简单的界面实现
public class MainActivity extends FragmentActivity implements IgetWeatherView{
private Button request_btn;
private TextView display_weather_tv;
private WeatherPresenter weahterPresenter;
private ProgressBar progressbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
}
private void initView() {
display_weather_tv = (TextView) findViewById(R.id.display_weather_tv);
request_btn= (Button) findViewById(R.id.request_btn);
progressbar= (ProgressBar)findViewById(R.id.progressbar);
weahterPresenter= new WeatherPresenter(this);
}
private void initListener() {
request_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
weahterPresenter.getWeatherInfo(MainActivity.this);
}
});
}
@Override
public void showLoading() {
progressbar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
progressbar.setVisibility(View.GONE);
}
@Override
public void getWeatherFialed() {
Toast.makeText(this,"获取天气信息失败",Toast.LENGTH_SHORT).show();
}
@Override
public void getWeatherSuccess(WeatherBean.WeatherInfo weatherInfo) {
Log.d("TAG", "city is " + weatherInfo.getCity());
Log.d("TAG", "temp is " + weatherInfo.getTemp());
Log.d("TAG", "time is " + weatherInfo.getTime());
}
@Override
public void displayWeatherInfo( WeatherBean.WeatherInfo weatherInfo) {
display_weather_tv.setText("city is "+weatherInfo.getCity() +
"temp is "+weatherInfo.getTemp() +
"time is "+weatherInfo.getTime());
}
总结:
上面我们总结了三种架构模式的特点及优缺点,并重点对MVP的模式做了实例讲解。真正要理解各种架构模式必须要自己动手实践才行。
上述例子中只贴了核心代码,其中网络是自己分装的volley请求。使用MVP模式后发现,代码量和类确实变多了,不过回过头来,看activity的代码,确实简洁了很多。可能这个例子还不是很明显,不过对于项目比较大,界面逻辑比较多的情况下,使用MVP的模式的确能放大其优势。不过,最终,在自己的项目中该使用什么模式,还是取决于具体项目大小以及复杂程度,还有团队的协作情况。总之,没有最好的模式,只有最适合团队的模式。不过,作为一名android开发者,每一种设计模式都应该了解,这样在将来要用到的时候才能随心所欲的使用,不至于犯怵。好了,关于Android架构模式就总结到这里,大家有什么疑问的,欢迎一起交流学习。