網站首頁 編程語言 正文
1.什么是MVP?
Google在2016年推出了官方的Android MVP架構Demo,本文主要分析一下官方的MVP Demo,并且借由自己的一些經驗,提出一些學習過程中,遇到的問題和自己的改進、封裝措施。
MVP架構已經推出很多年了,現在已經非常普及了,我在這里就不過多介紹,簡單的說,它分為以下三個層次:
- Model:數據模型層,主要用來數據處理,獲取數據;
- View:顯示界面元素,和用戶進行界面交互;
- Presenter: 是Model和View溝通的橋梁,不關心具體的View顯示和Model的數據處理。View層中所有的邏輯操作都通過Presenter去通知Model層去完成,Model中獲取的數據通過Presenter層去通知View層顯示。 MVP架構最大的好處,就是把傳統MVC架構中View層和Control層的復雜關系完全解耦,View層只關心界面顯示相關的工作即可,Model層僅獲取數據,處理邏輯運算即可,各司其職,而不用關心其他工作。而且大家發現沒有,這樣設計的話,很好寫單元測試代碼,針對于View、Presenter、Model層我們可以分別選用適合的單元測試框架,不用再像MVC/MVVM一樣,由于代碼混在或者分離在多處,到處無法“單一職責”的去完成每個類的單元測試代碼編寫。
在剛剛接觸android的時候,或者說,現在依然有很大一部分的APP開發者,在開發過程中,總是習慣在一個Activity、Fragment中幾乎完成了所有的功能。例如網絡請求、數據加載、業務邏輯處理、界面加載、界面動畫。 后來漸漸的我們接觸到了MVC、MVP各種官方的框架,懂得了模塊的分離、解耦(MVC),懂得了通過依賴于抽象去分離各個模塊徹底解耦(MVP)。 但是官方的MVP框架的確已經幫我們做了很多,但是依然不夠,接下來,我們基于官方給的MVP框架,結合六大基本原則,去封裝更加適宜于項目的MVP框架。 整體設計模式Demo代碼
2.Google官方的MVP
官方Demo怎么去做的?
我們為了方便去分析,我這里簡化代碼,我們逐步分析,BaseView 和 BasePresenter,BaseView谷歌是這么寫的(其實就是view的接口,展示view),以下樣例為了理解,我簡化處理了部分代碼 BaseView.java
package com.itbird.design.principle.mvp.google; /** * Google Demo * Created by itbird on 2022/2/25 */ public interface BaseView<T> { //View中,設置presenter對象,使View可以持有Presenter對象引用 void setPresenter(T presenter); }
BasePresenter
package com.itbird.design.principle.mvp.google; /** * Google Demo * Created by itbird on 2022/2/25 */ public interface BasePresenter { }
TaskDetailContract
package com.itbird.design.principle.mvp.google; /** * Google Demo * Created by itbird on 2022/2/25 */ public interface TaskDetailContract { interface View extends BaseView<Presenter> { //界面UI刷新方法 void updateTextView(String s); } interface Presenter extends BasePresenter { void loadDataFromModel(); } }
TaskGoogleActivity
package com.itbird.design.principle.mvp.google; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.itbird.design.R; public class TaskGoogleActivity extends AppCompatActivity implements TaskDetailContract.View { private static final String TAG = TaskGoogleActivity.class.getSimpleName(); private TaskDetailContract.Presenter mPresenter; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.textview); Log.e(TAG, TAG + " onCreate"); new TaskGooglePresenter(this); } @Override public void setPresenter(TaskDetailContract.Presenter presenter) { mPresenter = presenter; } @Override public void updateTextView(String s) { mTextView.setText(s); } }
TaskGooglePresenter
package com.itbird.design.principle.mvp.google; public class TaskGooglePresenter implements TaskDetailContract.Presenter { private static final String TAG = TaskGooglePresenter.class.getSimpleName(); TaskDetailContract.View mView; public TaskGooglePresenter(TaskDetailContract.View view) { mView = view; mView.setPresenter(this); } @Override public void loadDataFromModel() { //TODO :loaddata,此處可以用model、或者進行業務操作 //調用界面方法,進行數據刷新 mView.updateTextView("loaddata success!!!"); } }
小結 我們單從設計來說, Google MVP Demo的確向我們展示了MVP的優點:
- 1)業務、數據、視圖分離、解耦,從六大基本原則上來說,開閉、單一職責、里氏代換、依賴倒置、接口隔離、最少知道,基本都做到了。
- 2)由于完全分離,所以我們很方便針對于每層去選取對應的單元測試模式,例如針對于數據層可以使用(Junit+Mockito)、視圖層可以使用(AndroidJunitRunner+Espresso)、業務層可以使用(Junit+Mockito)
- 3)通過Contract 契約類,完全將視圖、業務相關的接口,封裝放在了一個地方,簡單明了,針對于開發者來說,不容易忘記和輔助養成習慣
但是,但是對于我們實際開發使用來說,依然有以下幾點問題:
- 1)setPresenter接口完全沒有必要存在,因為Presenter對象一定是在View類中new出來的,我既然都有它自己的對象了,干嘛還要在Presenter內部,去調用mView.setPresenter(this);,再返回View中,再去保存一個引用,代碼看著很怪
- 2)我們知道MVP的精髓在與,P、V肯定要相互交互,他們互相要持有對方的引用,通過上面一點,我們知道Presenter對象一定是在View類中new出來的,所以View肯定有Presenter對象的引用,這個沒問題了。但是Presenter要想擁有View的引用,只能通過Presenter構造方法、或者Presenter內部有一個setView方法,讓開發者自己去調用setView或者實現帶參構造方法,從設計角度來說,這明顯是一個坑,因為一個框架的設計,是為了更加方便開發,而不是讓開發人員設置這個、設置那個、必須調用某個方法。既然是必須調用的方法,我們應該通過框架去內部消化掉,而不是給開發者制造麻煩。
- 3)Presenter中擁有View的引用,如果activity、fragment銷毀,但是presenter依然在執行某些任務,這樣會導致activity、fragment無法GC回收,導致內存泄露,甚至與崩潰,所以這也是一個框架必須解決的問題。
3.V1.1 My MVP V1
基于上面提出的三點,我們去優化Google的MVP框架。 我們首先將第一點和第二點通過抽象來解決一下。 IPresenter
package com.itbird.design.principle.mvp.v1; /** * 自定義MVP框架,BasePresenter * Created by itbird on 2022/2/25 */ public interface IPresenter { /** * 與view班定 * * @param view */ void onAttach(IView view); /** * 與view解綁 */ void onDetach(); /** * 是否與view已經班定成功 * * @return */ boolean isViewAttached(); /** * 獲取view * @return */ IView getView(); }
IView
package com.itbird.design.principle.mvp.v1; /** * 自定義MVP框架,BaseView * Created by itbird on 2022/2/25 */ public interface IView { }
接下來是借助activity生命周期,對presenter的初始化進行封裝 BaseActivity
package com.itbird.design.principle.mvp.v1; import android.app.Activity; import android.os.Bundle; import androidx.annotation.Nullable; /** * Created by itbird on 2022/3/29 */ public abstract class BaseActivity extends Activity implements IView { IPresenter mPresenter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPresenter = createPresenter(); if (mPresenter != null) { mPresenter.onAttach(this); } } @Override protected void onDestroy() { super.onDestroy(); if (mPresenter != null) { mPresenter.onDetach(); mPresenter = null; } } abstract IPresenter createPresenter(); }
BasePresenter
package com.itbird.design.principle.mvp.v1; import java.lang.ref.WeakReference; /** * Created by itbird on 2022/3/29 */ public class BasePresenter<V extends IView> implements IPresenter { WeakReference<V> mIView; @Override public void onAttach(IView iView) { mIView = new WeakReference<>((V) iView); } @Override public void onDetach() { mIView = null; } @Override public V getView() { if (mIView != null) { mIView.get(); } return null; } @Override public boolean isViewAttached() { return mIView != null && mIView.get() != null; } }
4.V1.2 My MVP V2
看上圖類圖,我們依然發現有一些不滿足的點:
1)activity中,依然需要初始化mPresenter
@Override IPresenter createPresenter() { mTaskPresenter = new TaskMyPresenter(); return mTaskPresenter; }
是否可以做到在activity中,自己像presenter中調用view一樣,自己一句話getView就可以搞定
2)觀察類圖,其實IView、IPresenter沒有必要存在,因為畢竟只是baseActivity、basePresenter的行為
所以改造如下: BasePresenter
package com.itbird.design.principle.mvp.v2; import java.lang.ref.WeakReference; /** * Created by itbird on 2022/3/29 */ public abstract class BasePresenter<V> { WeakReference<V> mIView; public void onAttach(V iView) { mIView = new WeakReference<>(iView); } public void onDetach() { mIView = null; } public V getView() { if (mIView != null) { mIView.get(); } return null; } public boolean isViewAttached() { return mIView != null && mIView.get() != null; } }
BaseActivity
package com.itbird.design.principle.mvp.v2; import android.app.Activity; import android.os.Bundle; import androidx.annotation.Nullable; /** * Created by itbird on 2022/3/29 */ public abstract class BaseActivity<V, T extends BasePresenter<V>> extends Activity { T mPresenter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPresenter = initPresenter(); if (mPresenter != null) { mPresenter.onAttach((V) this); } } @Override protected void onDestroy() { super.onDestroy(); if (mPresenter != null) { mPresenter.onDetach(); mPresenter = null; } } public T getPresenter() { return mPresenter; } abstract T initPresenter(); }
此時,契約類,不再需要依賴BasePresenter/BaseView相關接口,而且View中也可以自己獲取到presenter的引用了。
package com.itbird.design.principle.mvp.v2; /** * my Demo * Created by itbird on 2022/2/25 */ public interface TaskMyContract { interface View { //界面UI刷新方法 void updateTextView(String s); } interface Presenter { void loadDataFromModel(); } }
package com.itbird.design.principle.mvp.v2; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import com.itbird.design.R; public class TaskMyActivity extends BaseActivity<TaskMyContract.View, TaskMyPresenter> implements TaskMyContract.View { private static final String TAG = TaskMyActivity.class.getSimpleName(); TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = findViewById(R.id.textview); Log.e(TAG, TAG + " onCreate"); mPresenter.loadDataFromModel(); } @Override TaskMyPresenter initPresenter() { return new TaskMyPresenter(); } @Override public void updateTextView(String s) { mTextView.setText(s); } }
此時的類圖,是否更加明確,而且上面各個問題都已經得到了解決。
整體設計模式Demo代碼
原文鏈接:https://juejin.cn/post/7169418953385574408
相關推薦
- 2022-07-25 利用正則表達式匹配浮點型數據_正則表達式
- 2023-03-29 nginx部署前端項目的超級詳細步驟記錄_nginx
- 2022-10-19 報錯No?module?named?numpy問題的解決辦法_python
- 2022-09-13 GO語言包管理工具go?mod以及包詳解_Golang
- 2022-03-15 Docker run App的時候 WARNING: IPv4 forwarding is dis
- 2022-04-21 R語言繪制帶ErrorBar的分組條形圖代碼的分享_R語言
- 2021-11-18 詳解C++中inline關鍵字的作用_C 語言
- 2022-03-27 Android自定義模擬時鐘控件_Android
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支