MVP架構模式詳解


一.為什么需要軟件設計模式?

我們先來定義什么是好的軟件架構:
  1. 軟件架構上具有明確的分工,各個模塊的功能職責平衡分配,且明確。
  2. 可測試性,通常良好的軟件架構都具備良好的可測試性。
  3. 良好的易用性,維護成本低。
為什么需要模塊分工?

良好的模塊分工,可以大大簡化我們對代碼的理解難度。雖然通過大量的開發工作,可以訓練我們的大腦去分析越來越復雜的邏輯,但是人總有極限,而且簡單的邏輯更容易理解、不容易出錯,所以,遵循單一職責原則,將復雜的業務邏輯分解。

為什么需要良好的可測試性?

對於深知單元測試好處的開發者來說,這並不是一個問題。單元測試可以大大地減少程序運行時才能發現的問題,這通常可以節省「用戶反饋」->「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模式存在以下問題:

 

  1. 視圖與控制器間的過於緊密的連接

視圖與控制器是相互分離,但卻是聯系緊密的部件,視圖沒有控制器的存在,其應用是很有限的,反之亦然,這樣就妨礙了他們的獨立重用。

  1. 視圖對模型數據的低效率訪問

依據模型操作接口的不同,視圖可能需要多次調用才能獲得足夠的顯示數據。對未變化數據的不必要的頻繁訪問,也將損害操作性能。

  1. 不太友好的單元測試

特別是App上做單元測試的時候很多東西依賴與系統框架,沒法脫離用戶接口來測試這些邏輯單元。使用MVP對Presenter的測試--不需要使用自動化的測試工具。 我們可以在Model和View都沒有完成時候,就可以通過編寫Mock Object(即實現了Model和View的接口,但沒有具體的內容的)來測試Presenter的邏輯。

基於以上幾點問題,就衍生了出了一些軟件設計模式如MVP,MVVW。為什么是MVP?我們先看下面這張圖

 

 

 

 
  1. MVP分離了view和model層,Presenter層充當了橋梁的角色,View層只負責更新界面即可,這里的View我們要明白只是一個viewinterface,它是視圖的接口,這樣我們在做單元測試的時候可以非常方便編寫Presenter層代碼。關於mvp的代碼測試,我們可以參考google給出的代碼,google現在也在推行mvp,為此google發布了一些案例,大家可參考這里android-architecture
     
    mvp-clean.png
  1. 厚重的Controller層代碼也得到了釋放,之前我們開發的時候會對UIViewController、Activity、Fragment編寫很多的業務邏輯,盡管大家會將Service層做分離,如net層,DB層等,但還是無法避免類似的問題,activity uicontroller無法重復利用是非常難以忍受的。
  2. 有一點還需要注意,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
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM