目录:
- MVP与MVC的关系
- 实践MVP:从用户点击到数据展示
写在前面的话:刚进入公司实习一周,要学的东西很多。前期的主要技术点有EventBus/RxJava/RxAndroid/Retrofit/Okhttp/Glide/MVP/Arouter;后期的主要技术点有rexxar/react native/组件化。
1.MVP与MVC的关系
MVC、MVP和MVVM都是为了解决图形界面应用程序复杂性管理问题而产生的应用架构模式。 图形界面的应用程序提供给用户可视化的操作界面,这个界面提供给数据和信息。用户输入行为(键盘/鼠标等)会执行一些业务逻辑,可能导致对应用程序数据的变更,数据的变更自然需要用户界面的同步变更以提供最准确的信息。
在开发应用程序时,为了更好的管理应用程序的复杂性,基于职责分离(Speration of Duties)的思想都会对应用程序进行分层。
- 1)把管理用户界面的层次成为View
- 2)应用程序的数据为Model。Model层对应用程序的业务业务逻辑无知,只保存数据结构和提供数据操作的接口。
有了View和Model的分层,人们就要着手解决两个问题:
- 1)响应用户操作的业务逻辑(例如排序)的管理
- 2)View如何同步Model的变更
MVC的依赖关系
MVC将应用程序分为Model、View、Controller层。其中Controller层是专门管理应用程序的业务逻辑。他们之间的依赖关系如下:
Controller和View都依赖Model层,Controller和View可以相互依赖。
MVC的调用关系
用户对View操作以后,View捕获到这个操作,会把处理的权力交给Controller(Pass calls);Controller接着会执行相关的业务逻辑,这些业务逻辑可能需要对Model进行相应操作;当Model变更之后,会通过观察者模式(Observer Pattern)通知View;View通过观察者模式收到Model变更的消息以后,会向Model请求最新的数据,然后重新更新页面。如下图:
注意:
- 1)View是把控制权交给Controller,自己不执行业务逻辑。
- 2)Controller执行业务逻辑并且操作Model,但不会直接操作View,可以说它是对View无知的。
- 3)View和Model的同步消息是通过观察者模式进行,而同步操作是由View自己请求Model的数据然后对试图进行更新。
特别注意: MVC的精髓在于第三点:Model的更新是通过观察者模式告诉View的,具体表现形式可以是Pub/Sub或者是触发Events。通过观察者模式的好处是:不同的MVC三角关系可能会有共同的Model,一个MVC三角中的Controller操作了Model以后,两个MVC三角的View都会接收到通知,然后更新自己。保持了依赖同一块Model的不同View显示数据的实时性和准确性。
MVC的优缺点
优点:
- 1)把业务逻辑全部分离到Controller中,模块化程度高,当业务逻辑变更的时候,不需要变更View和Model,只需要Controller换成另外一个Controller就行了(Swappable Controller)。
- 2)观察者模式可以做到多视图同时更新。
缺点:
- 1)Controller测试困难。因为试图同步操作是由View自己执行的,而View只能在有UI的环境下运行。在没有UI环境下对Controller进行单元测试的时候,Controller业务逻辑的正确性是无法验证的;Controller更新Model的时候,无法对View的更新操作进行断言。
- 2) View无法组件化。View是强以来特定的Model的,如果需要把这个View抽出来作为一个另外一个应用程序可复用的嘴贱就困难了。因为不同程序的Domain Model是不一样的。
MVP
MVP模式有两种:
- 1)Passive View
- 2)Supervising Controller
由于PV模式在项目中广泛运用,本文只介绍PV模式。
MVP(Passive View)的依赖关系
MVP模式把MVC模式中的Controller换成了Presenter,MVP层次之间的依赖关系如下:
MVP的重要突破是打破了原来对于Model的依赖,其余的依赖关系和MVC模式一致。
MVP(Passive View)的调用关系
和MVC模式一样,用户对View的操作都会从View交给Presenter。Presenter同样的会执行相应的业务逻辑,并且对model进行相应的操作;而这个时候Model也是通过观察者模式把自己变更的消息传递出去,但是是传给Presenter而不是View。Presenter获取到数据以后,通过View提供的接口更新界面。
注意:
- 1)View不再负责同步的逻辑,而是由Presenter负责。Presenter中既有业务逻辑也有同步逻辑。
- 2)View需要提供操作界面的接口给Presenter进行调用。(关键)
MVP(Passive View)的优缺点
优点:
- 1)便于测试。Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候,可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter业务逻辑的正确性。
- 2)View可以进行组件化。在MVP中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务逻辑完全唔知,它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。
缺点:
- 1)Presenter中除了业务逻辑意外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来比较困难。
MVVM
MVVM可以看作一种特殊的MVP(Passive View)模式,或者说是MVP模式的一种改良。 MVVM代表的是Model-View-ViewModel。其中ViewModel的含义就是Model of View,视图的模型。它的含义包含了领域模型(Domain Model)和视图的状态(State)。在图形界面应用程序当中,界面所提供的信息可能不仅仅包含应用程序的领域模型。还可能包含一些领域模型不包含的视图状态,例如电子表格程序上需要显示当前排序的状态是顺序的还是逆序的,而这是Domain Model所不包含的,但也是需要显示的信息。
可以简单把ViewModel理解为页面上所显示内容的数据抽象,和Domain Model不一样,ViewModel更适合用来描述View。
MVVM的依赖
MVVM的调用关系
MVVM的调用关系和MVP一样。但是,在ViewModel当中会有一个叫Binder,或者是Data-binding engine的东西。以前全部由Presenter负责的View和Model之间数据同步操作交由给Binder处理。你只需要在View的模板语法当中,指令式地生命View上显示的内容和Model的哪一块数据绑定的。当ViewModel对Model进行更新的时候,Binder会自动把数据更新到View上去,当用户对View进行操作(例如表单输入),Binder也会自动把数据更新到Model上去。这种方式称为Two-way data-binging,双向数据绑定。可以简单而不恰当的理解为一个模板引擎,但是会根据数据变更实时渲染。
简而言之,MVVM把View和Model的同步逻辑自动化了。以前Presenter负责的View和Model同步不再手动地进行操作,而是交由框架所提供的Binder进行负责。只需要告诉Binder,View显示的数据对应的是Model哪一部分即可。
MVVM的优缺点
优点:
- 1)提高可维护性。解决了MVP大量的手动View和Model同步的问题,提供双向绑定机制,提高了代码的可维护性。
- 2)简化测试。因为同步逻辑交由Binder做的,View跟着Model同时变更,所以只需要保证Model的正确性,View就正确。大大较少了对View同步更新的测试。
缺点:
- 1)过于简单的图形界面不适用,或者牛刀杀鸡。
- 2)对于大型的图形应用程序,视图状态较多,View Model的构建和维护的成本会比较高。
- 3)数据绑定的生命是指令式地写在View的模板当中,这些内容是内办法去打断点debug的。
2.实践MVP:从用户点击到数据展示
本例子实现了从点击一个按钮开始,获取奥迪品牌下的所有车型信息。
网络请求说明:
URL:http://api.aves.auto.sohuno.com/car/brands/191/models
GET /car/brands/191/models HTTP/1.1
响应:
{
"一汽奥迪": [{
"id": 2374,
"name": "奥迪A4L",
"minPriceGuide": 29.28,
"maxPriceGuide": 40.98,
"whiteGroundFocusPic": "http://m2.auto.itc.cn/c_pad,w_150,h_100,red_255,green_255,blue_255/logo/model/2374.jpg"
}, {
"id": 2051,
"name": "奥迪A6L",
"minPriceGuide": 40.6,
"maxPriceGuide": 69.8,
"whiteGroundFocusPic": "http://m3.auto.itc.cn/c_pad,w_150,h_100,red_255,green_255,blue_255/logo/model/2051.jpg"
},...],
"奥迪(进口)": [{
"id": 1553,
"name": "奥迪A8L",
"minPriceGuide": 93.78,
"maxPriceGuide": 130.98,
"whiteGroundFocusPic": "http://m1.auto.itc.cn/c_pad,w_150,h_100,red_255,green_255,blue_255/logo/model/1553.jpg"
}, {
"id": 3112,
"name": "奥迪A4 allroad",
"minPriceGuide": 41.8,
"maxPriceGuide": 46.88,
"whiteGroundFocusPic": "http://m4.auto.itc.cn/c_pad,w_150,h_100,red_255,green_255,blue_255/logo/model/3112.jpg"
},...],
...
}
第一步,根据请求格式和响应数据构建相关的Repository(Model层)
1)封装网络请求api类SearchCarAPI
public class SearchCarAPI {
private static class InstanceHolder {
static Api INSTANCE = ServiceFactory.createService("https://app.auto.sohu.com/", Api.class);
}
public interface Api {
//某品牌下的车型列表
@GET("car/brands/{brandId}/models")
Observable<Response<LinkedHashMap<String, List<CarModel>>>> getVehicleModel(@Path("brandId") int id);
}
public static Api getInstance() {
return InstanceHolder.INSTANCE;
}
}
2)构建车型类CarModel
/**
* 车型数据
*/
public class CarModel {
@SerializedName("id")
public String modelId; //id
@SerializedName("name")
public String modelName; //车型名称 (A3 Limousine
@SerializedName("minPriceGuide")
public String minPrice; //最低报价
@SerializedName("maxPriceGuide")
public String maxPrice; //最高报价
@SerializedName("whiteGroundFocusPic")
public String modelUrl; //车型焦点图Url
}
3)构建CarListDataSource接口,声明获取车辆列表的方法(Presenter->Model)和回调函数(Model->Presenter)
public interface CarListDataSource {
Observable<Response<LinkedHashMap<String, List<CarModel>>>> getCarList();
}
4)构建类CarListRepository,实例化CarListDataSource接口。
public class CarListRepository implements CarListDataSource {
private BaseActivity mBaseActivity;
private CarListRepository(BaseActivity activity){
mBaseActivity = activity;
}
public static CarListRepository getInstance(BaseActivity baseActivity){
return new CarListRepository(baseActivity);
}
@Override
public Observable<Response<LinkedHashMap<String, List<CarModel>>>> getCarList() {
return MySearchCarAPI.getInstance().getVehicleModel(191);
}
}
第二步,构建View层
1)创建Contract接口,管理View提供给Presenter的接口和Presenter提供给View的接口
public interface CarListContract {
interface CarListView extends BaseView<CarListPresenter>{ // View提供给Presenter的接口,用于更新界面
void showCarList(LinkedHashMap<String, List<CarModel>> carModels); // 当获取到汽车列表信息时更新界面的方法
void showNetError(); // 当网络异常时更新界面的方法
}
interface CarListPresenter extends BasePresenter{ // Presenter提供给View的接口,用于View响应点击事件,请求获取数据
LinkedHashMap<String, List<CarModel>> getCarListData(); // 获取汽车列表执行的方法
}
}
2)构建Fragment或Activity,由于界面显示
public class GlSearchCarFragment extends BaseFragment implements CarListContract.CarListView{
private static final String TAG = "GlSearchCarFragment";
private TextView audiCarTxt, carListResponseTxt;
private CarListContract.CarListPresenter mCarListPresenter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCarListPresenter = new CarListPresenter(this,
CarListRepository.getInstance((BaseActivity) getActivity()));
}
@Override
protected int getLayoutResource() {
return R.layout.fragment_searchcar;
}
@Override
protected void onInitView() {
audiCarTxt = (TextView) findViewById(R.id.audi_car_txt);
carListResponseTxt = (TextView) findViewById(R.id.response_car_list_txt);
audiCarTxt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.e(TAG, "text click!");
carListResponseTxt.setText("暂无数据");
mCarListPresenter.getCarListData();
}
});
carListResponseTxt.setText("暂无数据");
}
@Override
public void setPresenter(CarListContract.CarListPresenter presenter) {
mCarListPresenter = presenter;
}
@Override
public void showCarList(LinkedHashMap<String, List<CarModel>> carModelMaps) {
if(carModelMaps.size()<=0){
carListResponseTxt.setText("未查到汽车!");
}else {
StringBuilder stringBuilder = new StringBuilder();
for(String key:carModelMaps.keySet()){
stringBuilder.append(key+"\n");
for(CarModel carModel:carModelMaps.get(key)){
String s = "汽车Id: " + carModel.modelId
+ "\n汽车名称:" + carModel.modelName
+ "\n最高报价:" + carModel.maxPrice
+ "\n最低报价:" + carModel.minPrice;
stringBuilder.append(s+"\n");
}
stringBuilder.append("\n");
}
carListResponseTxt.setText(stringBuilder.toString());
}
}
@Override
public void showNetError() {
carListResponseTxt.setText("网络错误,请重试!");
}
}
3)界面布局文件fragment_searchcar.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">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="点击:"
android:textSize="24sp" />
<TextView
android:id="@+id/audi_car_txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="奥迪"
android:textSize="24sp"
android:gravity="center"
android:background="@color/account_fragment_sign_button_color" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="响应:"
android:textSize="24sp"
android:layout_margin="10dp" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/response_car_list_txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="暂无数据"
android:textSize="18sp"
android:background="@color/cY1"/>
</ScrollView>
</LinearLayout>
第三步,构建Presenter层,关联View与Model
1)创建CarListPresenter类,实例化CarListContract.CarListPresenter接口,将View与Model关联起来。
public class CarListPresenter implements CarListContract.CarListPresenter {
private CarListContract.CarListView mView;
private CarListRepository mCarListRepository;
public CarListPresenter(CarListContract.CarListView carListView, CarListRepository carListRepository){
mView = carListView;
mCarListRepository = carListRepository;
initCallBack();
}
private void initCallBack(){}
@Override
public void start() {}
@Override
public void getCarListData() {
mCarListRepository.getCarList().observeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new ResponseSubscriber<LinkedHashMap<String, List<CarModel>>>() {
@Override
public void onSuccess(LinkedHashMap<String, List<CarModel>> feeds) {
mView.showCarList(feeds);
}
@Override
public void onFailure(Throwable error) {
mView.showNetError();
}
});
}
}
至此利用MVP框架完成了从点击到数据获取展示的全部过程。项目中用到的RxJava和Retrofit框架还需要好好学习,既要会用,也要知其原理。相关框架的学习笔记,随后更新。