MVP架构模型的一些延伸笔记
零、前言
Activity是什么?
Activity是上帝类。不考虑架构的话,Activity类再开发中会越来越多堆积如山。因为在Android中,允许View和其他线程共存于Activity。这样就造成Activity中同时存在业务逻辑和UI逻辑。增加测试和维护成本,这样肿胀的Activity,还会引起其他问题,比如A和F的生命周期变得复杂不好控制或是数据绑定问题。
Activity负担如此的重,既要初始化控件又要进行逻辑代码的展示,相当于Activity充当了Controller和部分Model的角色,这样违背了单一职责的原则!所以在MVC基础上衍生了MVP!
一、MVP是什么?
MVP架构师google开源的一个设计模式,目的是为了将代码更加优雅清晰的呈现出来。为了更好的细分视图层与模型层的功能,让View专注于数据的可视化以及与用户的节目交互,同时,让Model层只关心数据的业务处理,基于MVC的理念产生了MVP模型。通过接口实现和接口调用的方式,使得用更简洁的代码完成功能的创建。
二、MVP各个是什么?
M层:model层,进行处理处理,负责数据访问。数据可以是远端Server API、本地数据库、sharedPreference等;
V层:View层,进行数据显示与用户交互,将数据传递给View层,通过View层显示数据。同时,View层的点击事件等在这里进行操作,但设计业务的数据层交还给model层处理;
ps:View interface,View层实现该接口,通过View interface与Presenter进行交互,降低耦合,方便进行单元测试;
P层:Presenter层,连接(适配)View层和Model层的桥梁。通过p层连接M层和V层进行通信。M层获取到数据后交给P层,由P层传递给View层显示;同理,view层各类事件处理通过P层去通知M层,让其进行处理。
View是UI线程;Presenter是View和Model之间的适配器。UseCase or Domain在Model层中,负责从实体获取或载入数据,规则:
ps:高层接口不能,不应该,并且必须不应该尝试去了解底层接口的细节,这就是面向对象的抽象方式,并且细节应该隐藏掉。
依赖规则?
Uncle Bob的“The Clean Architecture”描述了依赖的规则是什么。
同心圆将软件划分为不同的区域,一般的,随着层级的深入,软件的等级也就越高。外圆是实现机制,内圆是核心策略。
Entities:
- 可以是一个持有方法函数的对象
- 可以是一组数据结构或是方法函数
- 并不重要,能在项目中被不同应用程序使用即可
UseCases
- 包含特定于应用程序的业务规则
- 精心编排流入Entity or 从Entity 流出的数据
- 智慧Entity直接使用项目范围内的业务规则,从而实现UseCase目标
Presenters、Controllers
- 将UseCases和Entity中的数据转换成格式最方便的数据
- 外部系统,如数据库或是网页能够方便的使用这些数据
- 完全包含GUI的MVC架构
External Interfaces,UI,DB
- 所有的细节逻辑
- 数据库细节、Web框架细节等
三、MVP的优点
1、降低耦合度,隐藏数据逻辑,减轻Activity 或是Fragment的压力,让其更多的只关注处理生命周期任务,使得代码更加简洁明了
ps:View层的实现Activity或是fragment基本上只处理findViewById,setListener之类的代码,Ui逻辑都通过View接口的实现,和P层对象的引用
2、模块职责划分明显,视图逻辑和业务逻辑分别抽象到V层和P层的接口中去,提高代码可阅读性,复用度较高,灵活性高
3、方便测试驱动开发
ps:由于业务逻辑都在Presenter里,可以写一个PresenterTest实现类继承Presenter的接口,就能进行单元测试了
4、减少Activity的内存泄露问题
ps:采用传统的开发模式,很多异步任务和对UI的操作都是在Activity的进行的,比如下载一个图片,下载成功的回调里把图片加载到Activity的ImageView里面,异步任务保留着对Activity的引用。这样即使Activity已经被销毁,这些异步任务可能仍然保留着对Activity实例的引用,系统也就无法回收这个Activity的实例,结果就造成了Activity Leak ,Activity对象往往是在堆里占据最多内存的,所以系统优先回收级高,如果有AL现象,很容易造成内存不足
使用MVP模型,只要在onDestroy()中分离异步任务对Activity的引用就能更好的避免Activity leak
说了这么多优点 ,那MVP的缺陷呢?
最明显的创建一个Activity需要配合创建多个接口类和实现类,每个操作都需要通过接口回调的方式进行,虽然逻辑清晰代码,同时也造成了类的增多和代码量的加大。
解决方案:
利用泛型封装一下MVP的base类,通过泛型所提供的类型去实例化View层和Presenter层,在继承封装好的基类中快速的使用MVP模式。注意的是通过泛型约束后,在继承的时候需要填写多个泛型值。
四、MVP设计思路
中心思想:面向接口编程,调用层使用接口对象,去调用接口方法;实现层去实现接口方法,并在调用层,实例化出来。
实例(1): 比如我们要做一个加载图片的抽象方法也就是下面的这片代码:
public interface ImageLoaderInterface<T extends View> extends Serializable {
void displayImage(Context context, String path, T imageView);
void displayImage(Context context, @DrawableRes Integer resId, T imageView);
T createImageView(Context context);
}
使用的时候就是直接去使用它的方法,比如我们要在adapter中使用他, 那么我们不需要在adapter中去实例化他,而是直接使用这个接口对象,在使用的地方调用接口的方法:
public ImageLoaderInterface imageLoaderInterface; public void showPic(ViewHolder holder) {
imageLoaderInterface.displayImage(context, addPicRes, holder.iv_pic);
}
在其他类中去写它的实现类,在adapter的初始化中把这个实现类去传递给他:
public abstract class ImageLoader implements ImageLoaderInterface<ImageView> {
@Override
public ImageView createImageView(Context context) {
return new ImageView(context);
}
}
public class Loader extends ImageLoader {
@Override
public void displayImage(Context context, String path, ImageView imageView) {
Glide.with(context).load(path).into(imageView);
}
@Override
public void displayImage(Context context, @DrawableRes Integer resId, ImageView imageView) {
imageView.setImageResource(resId);
}
}
adapter中写set方法,让外部调用 adapter.setImageLoaderInterface(new Loader());
mvp架构中,我们要把m层的接口方法抽象出来,p层的接口方法抽象出来,v层的接口方法抽象出来,同时分别写3个接口的实现类,(v层一般是activity或者fragment继承view层的接口),v层持有p的接口对象,p层持有v层和m层的接口对象,m层为p层的提供数据,这时也就形成了mvp架构,三者之间通过p层相互连接。
——————————————————————————————————————————————
再看一个简单的实例:
实例(2):首先给Activity的View层定义一个接口类:
/** * Interface classes for the Top view */
public interface TopView {
/** * Initialize the view. * * e.g. the facade-pattern method for handling all Actionbar settings */
void initViews();
/** * Open {@link DatePickerDialog} */
void openDatePickerDialog();
/** * Start ListActivity */
void startListActivity();
}
下面是 View层在Activity中的实现,并在Activity中实例化P层对象:
- TopActivity只是负责处理事件监听或者展示每个视图组件
- 所有的业务逻辑必须委托给Presenter类
- 在MVP中,View和Presenter是一一对应的在这里(在MVVM中是一对多的关系)
public class TopActivity extends Activity implements TopView {
// here we use ButterKnife to inject views /** * Calendar Title */
@Bind(R.id.calendar_title)
TextView mCalendarTitle;
private TopPresenter mTopPresenter; //P层对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_top);
ButterKnife.bind(this);
// Save TopPresenter instance in a meber variable field
mTopPresenter = new TopPresenter();
mTopPresenter.onCreate(this);
}
/* * Overrides method from the {@link TopView} interfaces */
@Override
public void initViews() {
// Actionbar settins
// set event listeners
}
@Override
public void openDatePickerDialog() {
DatePickerFragment.newInstance().show(getSupportFragmentManager(), DatePickerFragment.TAG);
// do not write logic here... all logic must be passed to the Presenter
mTopPresenter.updateCalendarDate();
}
@Override
public void startListActivity() {
startActivity(new Intent(this, ListActivity.class));
}
}
接下来是Presenter类,也就是P层,是连接View和Model的适配桥梁。在这里TopUseCase的saveCalendarDate()方法是对TopPresenter细节的隐藏。P层不需要关系数据结构或是业务逻辑,只需要在需要的时候可以通过对象调用方法将数据和节目进行连接即可。因此可以对TopUseCase即是Modle层进行单元测试,因为业务逻辑与视图是分离的。
public class TopPresenter {
@Nullable
private TopView mView; //View层对象
private TopUseCase mUseCase; //Model层对象
public TopPresenter() {
mUseCase = new TopUseCase();
}
public void onCreate(@NonNull TopView topView) {
mView = topView;
// here you call View's implemented methods
mView.initViews();
}
public void updateCalendarDate() {
// do not forget to return if view instances is null
if (mView == null) {
return;
}
// here logic comes
String dateToDisplay = mUseCase.getDateToDisplay(mContext.getResources());
mView.updateCalendarDate(dateToDisplay);
// here you save date, and this logic is hidden in UseCase class
mUseCase.saveCalendarDate();
}
}
在这里推荐的测试类库是http://robolectric.org/ ,进行部分功能的单元测试工作。
——————————————————————————————————————————————
实例(3):一个p层去持有多个model方案,只有Presenter层需要特殊处理,其他同上方基础例子:
public class MoreModelPresenter implements MoreModelContract.Presenter {
private MoreModelContract.OneModel oneModel;
private MoreModelContract.TwoModel twoModel;
private MoreModelContract.ThreeModel threeModel;
private MoreModelContract.View view;
public MoreModelPresenter(MoreModelContract.OneModel oneModel, MoreModelContract.TwoModel twoModel,
MoreModelContract.ThreeModel threeModel, MoreModelContract.View view) {
this.oneModel = oneModel;
this.twoModel = twoModel;
this.threeModel = threeModel;
this.view = view;
}
@Override
public void getData1() {
view.showData1(oneModel.doDataOne());
}
@Override
public void getData2() {
view.showData1(twoModel.doDataTwo());
}
@Override
public void getData3() {
view.showData1(threeModel.doDataThree());
}
}
单V多p多m架构
public class MainActivity extends AppCompatActivity implements MorePresenterContract.View {
private MorePresenterOne morPresenterOne;
private MorePresenterTwo morePresenterTwo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
morPresenterOne = new MorePresenterOne(MorePresenterModelOne.getInstance(), this);
morePresenterTwo = new MorePresenterTwo(MorePresenterModelTwo.getInstance(), this);
findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
morPresenterOne.getData1();
} });
findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
morePresenterTwo.getData2();
} }); }
@Override
public void showData1(String str) {
Toast.makeText(this,str,Toast.LENGTH_SHORT).show();
}
@Override
public void showData2(String str) {
Toast.makeText(this,str,Toast.LENGTH_SHORT).show();
}
}
//mvp接口类
public interface MorePresenterContract {
interface View { //显示数据
void showData1(String str);
void showData2(String str);
}
interface PresenterOne { //通知model要获取数据并把model返回的数据交给view层
void getData1();
}
interface PresenterTwo { //通知model要获取数据并把model返回的数据交给view层
void getData2();
}
interface ModelOne { //获取数据
String doData1();
}
interface ModelTwo { //获取数据
String doData2();
}
}
//第一个p
public class MorePresenterOne implements MorePresenterContract.PresenterOne {
private MorePresenterContract.ModelOne modelOne;
private MorePresenterContract.View view;
public MorePresenterOne(MorePresenterContract.ModelOne modelOne, MorePresenterContract.View view) {
this.modelOne = modelOne;
this.view = view;
}
@Override
public void getData1() {
view.showData1(modelOne.doData1());
}
}
//第二个p
public class MorePresenterTwo implements MorePresenterContract.PresenterTwo {
private MorePresenterContract.ModelTwo modelTwo;
private MorePresenterContract.View view;
public MorePresenterTwo(MorePresenterContract.ModelTwo modelTwo, MorePresenterContract.View view) {
this.modelTwo = modelTwo;
this.view = view;
}
@Override
public void getData2() {
view.showData2(modelTwo.doData2());
}
}
//model就是普通写法
public class MorePresenterModelOne implements MorePresenterContract.ModelOne {
private static MorePresenterModelOne morePresenterModelOne;
public static MorePresenterModelOne getInstance() {
if (morePresenterModelOne == null) {
morePresenterModelOne = new MorePresenterModelOne();
}
return morePresenterModelOne;
}
@Override
public String doData1() {
return "MorePresenterModelOne";
}
}
model模块拆分,将大量复用的model单独拆分出来,让这个model可以去被其他model调用
public interface ModelIncludeModeContract {
interface View {
//显示数据
void showData(String str);
}
interface Presenter {
//通知model要获取数据并把model返回的数据交给view层
void getData();
}
interface Model {
//获取数据
String doOtherDataOne();
// String doOtherDataTwo();
}
}
public class OneModelIncludeOther implements ModelIncludeModeContract.Model {
private IOtherMode otherMode;
private IOtherModeTwo otherModeTwo;
//主model使用时去初始化其他子model,让主model可以调用子model的方法
public OneModelIncludeOther(IOtherMode otherMode, IOtherModeTwo otherModeTwo) {
this.otherMode = otherMode; this.otherModeTwo = otherModeTwo;
}
@Override
public String doOtherDataOne() {
return otherMode.otherData();
}
@Override
public String doOtherDataTwo() {
return otherModeTwo.otherDataTwo();
}
}
//第一个子model的接口类
public interface IOtherMode { String otherData();
}
//第二个子model的接口类
public interface IOtherModeTwo { String otherDataTwo(); }
五、MVC、MVP、MVVM?
这些模式动机是一样的:那就是如何避免复杂混乱的代码逻辑,让执行单元测试变得更容易,创造高质量的代码及应用。
MVVM是Model-View-ViewModel的缩写。是由MVP模式与WPF结合的应用模式。
MVVM架构:
Model:代表你的基本业务逻辑
View:显示内容
ViewModel:将前面两者联系在一起的对象
一个ViewModel接口提供了两种东西:动作+数据;动作改变Model的下层(click listener,监听文字改变的listener等),而数据则是Model的内容。ViewModel在改变内容之后通知binding framework内容发生了改变,然后framework自动更新和那些内容绑定的View。这样可以在没有View的情况下进行ViewModel的测试。
MVVM的优点
1、低耦合
View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model并不受影响,反之也不会受影响。
2、可重用性
可以把一些视图逻辑放在ViewModel里面,让很多View重用这些类似的视图逻辑
3、独立开发
开发人员可以专注于业务逻辑和数据的开发
4、更好的测试性
针对于ViewModel的测试将更容易于没有界面的情况
(1)MVP与MVC一个重大区别:
在MVP中View并不直接使用Model,也就是View层与Model层没有任何直接连接,必须通过P层进行通信进行,所有交互都发生在Presenter内部。
而在MVC中View会从直接Model中读取数据而不是通过Controller。View层可以直接访问Model层,从M层获取数据,这样就不可避免的涉及到一些业务逻辑的处理。在MVC模型里,M层不依赖于View层,但是V层有时候确实依赖于M层的。在V层里的业务数据处理也导致了不能更好的代码复用。
(2) MVVM和MVP的区别:
MVVM模式将MVP中的Presenter改名为ViewModel,唯一的区别是,它采用了双向绑定(data-binding);View的变动,自动反映在ViewModel上,反之亦然。开发时候不用处理接受不了事件和View更新的工作。
参考资料及链接文档:
The Clean Architecture(译者注:清晰架构。译文) - Uncle Bob
这篇文章由Uncle Bob撰写,描述了依赖规则的样子和它们之间的组件是如何工作的。我从一开始谈论的那张图表的灵感就来源于他的文章,虽然这篇文章不是针对Android开发的,但是同往常一样,字里行间蕴藏着很多精辟的道理,所以,必读。
Architecting Android…The clean way? (译者注:Android中的清晰架构。译文)- Fernando Cejas
我认为这是在探索如何将MVP架构到Android开发专题中最著名,也是最受欢迎的博客。我也是从他那篇简单易读,书写良好的博客中偶然发现“MVP”这个名词的。他的示例代码托管在Github上,以便那些想要将MVP架构运用到正式App上的Android开发者clone到。
Android Architecture(译者注:Android架构) - Thanos Karpouzis
一个在Android项目中运用MVC,MVP,MVVM的简单指导。我从他的那篇普通却不平凡的文章中学到了很多,尤其是MVC,MVP和MVVM之间的不同。
Software Design patterns on Android English(Android开发中的软件设计模式) - Pedro Vicente Gómez Sánchez
这是一个在Karumi工作的高级Android开发工程师所讲的,他解释了一些MVP架构中的设计模式(如,渲染模式,仓库模式和命令模式)。如果你想深入理解MVC或者MVP,那这就使你要找的。
M — Model in MVC, MVP, MVVC in Android(MVC,MVP,MVVC架构中Model层在Android中的定义) - Artem Zinnatullin
如果你不还了解Model层中的JSON与SQL,或者不能透彻理解Model层的图像模型,这篇文章将带你进一步理解什么是Model层以及为什么Model层独立于其他层。其中“Model layer is solution”部分很好的解释了如何通过面向接口的方式编写测试。
再次感谢作者: