MVP架构模式详解
一.為什么需要軟件設計模式?
我們先來定義什么是好的軟件架構:
軟件架構上具有明確的分工,各個模塊的功能職責平衡分配,且明確。
可測試性,通常良好的軟件架構都具備良好的可測試性。
良好的易用性,維護成本低。
為什么需要模塊分工?
良好的模塊分工,可以大大簡化我們對代碼的理解難度。雖然通過大量的開發工作,可以訓練我們的大腦去分析越來越復雜的邏輯,但是人總有極限,而且簡單的邏輯更容易理解、不容易出錯,所以,遵循單一職責原則,將復雜的業務邏輯分解。
為什么需要良好的可測試性?
對于深知單元測試好處的開發者來說,這并不是一個問題。單元測試可以大大地減少程序運行時才能發現的問題,這通常可以節省「用戶反饋」->「Bug修復」->「新版本發布」->「用戶安裝新版本」這個耗時長達一周以上的過程。所以,程序的可測試性對于程序的穩定性是異常重要的。
為什么需要良好的易用性?
毋庸置疑,最好的代碼是還沒被寫出來的代碼。因此,越少的代碼,意味著越少的 bugs。這也意味著盡量以最少的代碼實現相同的功能,并非意味著這個開發者懶惰,同時,也不能不看維護成本而盲目贊同一個看似聰明的方案。
二.什么是MVP架構?
MVP是單詞Model View Presenter的首字母的縮寫,分別表示數據層、視圖層、發布層,它是MVC架構的一種演變。作為一種新的模式,MVP與MVC有著一個重大的區別:在MVP中View并不直接使用Model,它們之間的通信是通過Presenter (MVC中的Controller)來進行的,所有的交互都發生在Presenter內部,而在MVC中View會直接從Model中讀取數據而不是通過 Controller。
首先我們先看下傳統的MVC架構Model View Controller,我們把業務邏輯放到C層(ios的ViewController,android的Activity&Fragment),但是這里會引入另外一個問題,所有的邏輯都在C層,不可避免的會造成C層非常復雜,如果項目越來越大,C層的代碼會更加臃腫,維護起來也非常麻煩,而且也沒辦法==簡單的==做單元測試,試想做一個單元測試我們要加入多少邏輯代碼?
綜上所述我們總結下,現有的MVC模式存在以下問題:
視圖與控制器間的過于緊密的連接
視圖與控制器是相互分離,但卻是聯系緊密的部件,視圖沒有控制器的存在,其應用是很有限的,反之亦然,這樣就妨礙了他們的獨立重用。
視圖對模型數據的低效率訪問
依據模型操作接口的不同,視圖可能需要多次調用才能獲得足夠的顯示數據。對未變化數據的不必要的頻繁訪問,也將損害操作性能。
不太友好的單元測試
特別是App上做單元測試的時候很多東西依賴與系統框架,沒法脫離用戶接口來測試這些邏輯單元。使用MVP對Presenter的測試--不需要使用自動化的測試工具。 我們可以在Model和View都沒有完成時候,就可以通過編寫Mock Object(即實現了Model和View的接口,但沒有具體的內容的)來測試Presenter的邏輯。
基于以上幾點問題,就衍生了出了一些軟件設計模式如MVP,MVVW。為什么是MVP?我們先看下面這張圖
MVP分離了view和model層,Presenter層充當了橋梁的角色,View層只負責更新界面即可,這里的View我們要明白只是一個viewinterface,它是視圖的接口,這樣我們在做單元測試的時候可以非常方便編寫Presenter層代碼。關于mvp的代碼測試,我們可以參考google給出的代碼,google現在也在推行mvp,為此google發布了一些案例,大家可參考這里android-architecture
厚重的Controller層代碼也得到了釋放,之前我們開發的時候會對UIViewController、Activity、Fragment編寫很多的業務邏輯,盡管大家會將Service層做分離,如net層,DB層等,但還是無法避免類似的問題,activity uicontroller無法重復利用是非常難以忍受的。
有一點還需要注意,presenter是雙向綁定的關系,因此,在設計的時候就要注意接口和抽象的使用,盡可能的降低代碼的耦合度,這也是mvp的宗旨。
so,轉向mvp吧!我們先看下MVP幾個單詞的意思,以下是我個人的理解:
View: 是顯示數據(model)并且將用戶指令(events)傳送到presenter以便作用于那些數據的一個接口。View通常含有Presenter的引用。在Android開發中通常將Activity或者Fragment作為View層。
Model: 對于Model層也是數據層。它區別于MVC架構中的Model,在這里不僅僅只是數據模型。在MVP架構中Model它負責對數據的存取操作,例如對數據庫的讀寫,網絡的數據的請求等。
Presenter:對于Presenter層他是連接View層與Model層的橋梁并對業務邏輯進行處理。在MVP架構中Model與View無法直接進行交互。所以在Presenter層它會從Model層獲得所需要的數據,進行一些適當的處理后交由View層進行顯示。這樣通過Presenter將View與Model進行隔離,使得View和Model之間不存在耦合,同時也將業務邏輯從View中抽離。
三.實例
接下來我們看一個使用的用例吧,這個demo相對來說非常簡單,下面是項目的架構,一個Activity,一個Fragment,Data層主要負責獲取App已安裝的應用列表,AppListPresenter負責業務邏輯處理
我們先看下presenter,viewinterface的結構。
AppListFragment的代碼
1 public class AppListFragment extends Fragment implements AppViewInterface {
2
3 private Presenter presenter;
4
5 private List<PackageInfo> packageInfoList = new ArrayList<>();
6 private RecyclerView recyclerView;
7 private MyAppListRecyclerViewAdapter myAppListRecyclerViewAdapter;
8
9 @Override
10 public void showAppList(List<PackageInfo> packageInfos) {
11 if (packageInfos.isEmpty())
12 return;
13 packageInfoList.clear();
14 packageInfoList.addAll(packageInfos);
15 myAppListRecyclerViewAdapter.notifyDataSetChanged();
16 }
17
18 @Override
19 public void setPresenter(Presenter presenter) {
20 this.presenter = presenter;
21 }
22 }
代碼比較容易理解,AppListFragment實現了AppViewInterface接口,我們需要在Activity中把AppListPresenter和AppViewInterface雙向綁定。
接下來看下AppListPresenter層的代碼,這里只列出了幾個關鍵方法
1 public class AppListPresenter implements Presenter, LoaderManager.LoaderCallbacks<List<PackageInfo>>{
2
3 private AppViewInterface viewInterface;
4 private AppClassLoader appClassLoader;
5 private LoaderManager loaderManager;
6
7 private final int id = 0;
8 public AppListPresenter(AppViewInterface viewInterface, AppClassLoader appClassLoader,
9 LoaderManager loaderManager) {
10 this.viewInterface = viewInterface;
11 this.appClassLoader = appClassLoader;
12 this.loaderManager = loaderManager;
13 viewInterface.setPresenter(this);
14 }
15
16 @Override
17 public void loadInstallApps() {
18 //通過loadmanager提供的方法獲取安裝的應用列表
19 loaderManager.initLoader(id, null, this);
20 }
21
22 @Override
23 public void destory() {
24 loaderManager.destroyLoader(id);
25 }
26
27 @Override
28 public void onLoadFinished(Loader<List<PackageInfo>> loader, List<PackageInfo> data) {
29 //獲取到已安裝的應用列表,調用AppViewInterface的showAppList方法
30 viewInterface.showAppList(data);
31 }
32
33 @Override
34 public void launchApp(PackageInfo packageInfo) {
35 Intent intent = appClassLoader.queryLaunchIntent(packageInfo);
36 if (intent != null)
37 appClassLoader.getContext().startActivity(intent);
38 else
39 Toast.makeText(appClassLoader.getContext(), "Can not start the app", Toast.LENGTH_SHORT).show();
40 }
41 }
關鍵方法是loadInstallApps,這個方法在MainActivity的onCreate中調用
1 private Presenter appListPresenter;
2
3 @Override
4 protected void onCreate(Bundle savedInstanceState) {
5 super.onCreate(savedInstanceState);
6
7 setContentView(R.layout.activity_main);
8
9 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
10 setSupportActionBar(toolbar);
11
12 FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
13 AppListFragment appListFragment = AppListFragment.newInstance();
14 fragmentTransaction.add(R.id.fm, appListFragment);
15 fragmentTransaction.commit();
16
17 appListPresenter = new AppListPresenter(appListFragment, new AppClassLoader(getApplicationContext()),
18 getSupportLoaderManager());
19 //調用loadInstallApps
20 appListPresenter.loadInstallApps();
21 }
首先,我們獲取一個AppListFragment的實例,在AppListPresenter構造函數里面我們傳入AppViewInterface,同時在AppPresenter的構造函數中又將presenter注入到了AppViewInerface里面,這樣就實現了Presenter和ViewInerface雙向綁定,之后調用AppPresenter的loadInstallApps方法,在onLoadFinished回調里面又調用了AppViewInterface的showApps方法,這樣數據就顯示在界面。整個Activity和Fragment的代碼精簡了很多。
四.缺點
由于對視圖的渲染放在了Presenter中,所以視圖和Presenter的交互會過于頻繁。還有一點需要明白,如果Presenter過多地渲染了視圖,往往會使得它與特定的視圖的聯系過于緊密。一旦視圖需要變更,那么Presenter也需要變更了
作者:萌蠢的技術宅
鏈接:https://www.jianshu.com/p/4b754ea48a40
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
- 上一篇: 京东被曝“严整风纪”上热搜:非工作微信群
- 下一篇: 求问ps如何导出单个切片及PS导出所有的