MVP案例解析
来源:互联网 发布:mac os 彻底删除软件 编辑:程序博客网 时间:2024/06/13 16:34
- MVP的概念
- MVP的作用
- MVP的使用步骤
- 检验MVP模式写的是否规范的办法
- 案例解析
MVP的概念
MVP中,M层负责数据的读取和存储;V层负责视图逻辑的处理;P层负责业务逻辑的处理。同时,P层在处理业务逻辑时需要与V层和M层交互,所以会获取两层的引用实例,充当掌控者的角色。M层与V层彻底解耦的。
MVP是在MVC的基础上升级版,重在解耦,并不一定减少代码量。在大型项目中,引入MVP开发模式能充分体现出其强大功效。目前,一些敏捷开发的小型项目还是用MVC模式的较多。只是C层与V层的耦合度很容易变高,使得activity变得很臃肿。
MVP的作用
把Activity里的许多逻辑都抽离到View和Presenter的接口中去,并由具体的实现类来完成(用接口的形式因为有多种具体的实现),方便进行单元测试。
Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。同时把业务逻辑抽到Presenter中去。
注意:由于activity/fragment是V层的实现类,视图逻辑可通过接口的形式在其中实现。
MVP的使用步骤
创建V层的接口,根据视图逻辑列出对应的接口。视图逻辑:即用户的操作中与视图相关的。如获取清除用户输入的数据,显示隐藏进度条/对话框,页面跳转等。
创建P层的接口,根据业务逻辑列出对应的接口。业务逻辑:即用户的操作中与视图无关的。如点击滑动事件的处理等。
在V层的实现类中(即activity/fragment)实例化Presenter的实现类。并在Presenter的实现类中获取V层和M层的引用,并调用V层实现类的中的视图逻辑方法。
检验MVP模式写的是否规范的办法
- Activity除了FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。
- 试想着若界面改动或者业务逻辑改动,是否只影响到某一层逻辑的改动(逻辑复用操作产生的代码不是逻辑改动)。若M V P层都影响了,就说明没有解耦彻底。(注意:单个功能单个Presenter,若功能相近可单个Presenter内多个接口)
- P层的业务逻辑达到可以直接复用到其他V层而不需要该V层另做非视图逻辑外的处理(即逻辑复用)。
- V层不见业务逻辑处理。仅仅有视图逻辑处理。更不能有M层的逻辑处理。
注意:2中说明下,页面改动引起的业务逻辑改动在MVP模式中还不能达到仅在某一层中改动,MVVM是针对此种情况的改善版。
案例解析
案例情景:用户输入账户名和密码后,调用服务器登录接口,并显示返回数据。
从业务需求来看,罗列出各层的逻辑接口,并且各层统一由契约类约束,使用户对各层的功能一目了然,维护起来也方便。
契约类代码如下:
/** * Created by jinzifu on 2017/2/8. * 契约类来统一管理MVP模式中的Model View Presenter 的接口,使用户对各层的功能一目了然,维护起来也方便。 */public class LoginContract { public interface View { public void clear(); public String getUsername(); public String getPassword(); public void showProgress(); public void hideProgress(); public void error(); public void success(); public void showEmpty(); public void showInfo(String string); } public interface Presenter { public void loginP(); } public interface Model { public void loginM(String username, String password, OnLoginListener onLoginListener); }}
其中的clear接口我忘了用了[汗颜],可以看出各层都以接口的形式实现各层的逻辑。接口的形式便于扩展和单元测试,同一个接口可以有不同的实现类。
P层的实现类代码如下:
/** * Created by jinzifu on 2017/02/08 * 有时候为了实现一个复杂的业务,需要多个Presenter配合的。 * Presenter 获取的是V层的引用,因此是在UI线程中操作的。而M层的异步网络请求是在子线程的。 */public class LoginPresenterImpl extends BasePresenter<LoginContract.View> implements LoginContract.Presenter { private LoginModelImpl hiModel; private LoginContract.View view; public LoginPresenterImpl(LoginContract.View view) { hiModel = new LoginModelImpl(); this.view = view; } @Override public void loginP() { if (TextUtils.isEmpty(view.getPassword()) || TextUtils.isEmpty(view.getUsername())) { view.showEmpty(); return; } view.showProgress(); hiModel.loginM(view.getUsername(), view.getPassword(), new OnLoginListener() { @Override public void loginSuccess(User user) { view.success(); view.showInfo("账号:" + user.getUsername() + "\t\t密码:" + user.getPassword()); } @Override public void loginFailed() { view.error(); } @Override public void finish() { view.hideProgress(); } }); }}
可以看到在其构造方法内获取的V层和M层的引用(不是把activity的view传过来的意思,因为activity做了V层的实现类,这里获取的是V层的引用实例)。
实现了P层接口的loginP方法,在这里处理业务逻辑。并通过V层的引用调用V层的接口实现方法。如 view.showEmpty()、view.showProgress()等。
我们看到了该类继承了BasePresenter这个类,下面看下源码:
/** * Created by jinzifu on 2017/2/9. * BasePresenter基类,在此引入弱引用,防止内存泄漏 */public class BasePresenter<T> { protected Reference<T> viewRef; public void attachView(T view) { viewRef = new WeakReference<T>(view); } public void detachView() { if (viewRef != null) { viewRef.clear(); viewRef = null; } }}
可以看到在这个基类中,通过attachView将V层的引用由强引用转化为弱引用(强引用宁可报错也不会被垃圾回收器回收,弱引用可在内存不足时被回收),对内存泄漏做了规避。detachView方法是对该引用置空处理。
这里采用泛型是因为作为基类,用于适配不同类型的Presenter。
下面看看V层的实现类即activity的源码:
public class LoginActivity extends BaseMvpActivity<LoginContract.View, LoginPresenterImpl> implements LoginContract.View { @BindView(R.id.username) EditText username; @BindView(R.id.password) EditText password; @BindView(R.id.login) Button login; @BindView(R.id.show_info) TextView showInfo; private LoginPresenterImpl hiPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mvp); ButterKnife.bind(this); } @Override protected LoginPresenterImpl createPresenter() { hiPresenter = new LoginPresenterImpl(this); return hiPresenter; } @OnClick(R.id.login) public void onClick() { hiPresenter.loginP(); } @Override public void clear() { username.setText(""); password.setText(""); } @Override public String getUsername() { return username.getText().toString(); } @Override public String getPassword() { return password.getText().toString(); } @Override public void showProgress() { Toast.makeText(this, "加载中...", Toast.LENGTH_SHORT).show(); } @Override public void hideProgress() { Toast.makeText(this, "加载完毕...", Toast.LENGTH_SHORT).show(); } @Override public void error() { Toast.makeText(this, "请求失败...", Toast.LENGTH_SHORT).show(); } @Override public void success() { Toast.makeText(this, "请求成功...", Toast.LENGTH_SHORT).show(); } @Override public void showEmpty() { Toast.makeText(this, "账号和密码不能为空", Toast.LENGTH_SHORT).show(); } @Override public void showInfo(String string) { showInfo.setText(string); }}
V层做的仅仅是和视图逻辑有关的接口实现方法以及Presenter的初始化。避免了逻辑冗余。用户的操作中设计到业务逻辑的可以通过调用Presenter的接口方法实现。
通过createPresenter方法获取Presenter实例。父类BaseMvpActivity< V,T>在该类中实现V层由强引用变成弱引用。并在V生命周期结束时引用置空。
父类源码如下:
/** * Created by jinzifu on 2017/2/9. */public abstract class BaseMvpActivity<V, T extends BasePresenter<V>> extends Activity { protected T presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); presenter = createPresenter(); presenter.attachView((V) this); } @Override protected void onDestroy() { super.onDestroy(); presenter.detachView(); } protected abstract T createPresenter();}
项目中后续的需求变更,可以通过P层的接口变更或者多个接口组合实现业务逻辑的变动。也可以仅仅改变其实现类的业务逻辑。
思考:MVP是为了降低耦合而产生的,代码量及类文件也许会增多。不过对于大型项目后续的维护很有帮助。业务分离出V层,各个业务通过接口实现,接口又利于拓展和多个具体实现。便于同一套业务逻辑独立抽出来用在不同的V层。
如何利于新手上手,并且在满足降耦的基础上减少代码量,是接下来我们要做的事情。敬等后续更新。
- MVP案例解析
- 一个mvp小案例
- Google官网MVP案例
- MVP测试经典案例
- MVP模式的RecyclerView案例
- 强撸一个MVP案例
- MVP 模式实例解析
- MVP 模式实例解析
- MVP 模式实例解析
- TODO-MVP源码解析
- Android MVP解析实践
- MVC、MVP、MVVM解析
- Android MVP架构解析
- android:mvp+dagger2解析
- MVP模式解析实践
- Android MVP框架解析
- Android Dagger2+MVP+Retrofit2 开发案例
- 设计模式MVP案例----省市查询
- 学习日记:java SSM框架(Spring+SpringMVC+MyBatis)
- Listenter之使用ServletRequestListener和ServletRequestAttributeListener
- esri-leaflet部分瓦片缺失问题及解决办法
- Java与我的技术之路
- dev_t类型
- MVP案例解析
- ARM:g-sensor重力传感器电路原理图、I2C控制器数据手册、g-sersor芯片数据手册
- 个人Kafka使用
- 使用 HTTP/2 加速 Node.js 应用
- BZOJ 1116: [POI2008]CLO 并查集
- Gradle与项目构建
- Python与机器学习2
- 又一次Erlang 面试
- Java序列化与反序列化