一.為什么需要軟件設計模式?
我們先來定義什么是好的軟件架構:
- 軟件架構上具有明確的分工,各個模塊的功能職責平衡分配,且明確。
- 可測試性,通常良好的軟件架構都具備良好的可測試性。
- 良好的易用性,維護成本低。
為什么需要模塊分工?
良好的模塊分工,可以大大簡化我們對代碼的理解難度。雖然通過大量的開發工作,可以訓練我們的大腦去分析越來越復雜的邏輯,但是人總有極限,而且簡單的邏輯更容易理解、不容易出錯,所以,遵循單一職責原則,將復雜的業務邏輯分解。
為什么需要良好的可測試性?
對於深知單元測試好處的開發者來說,這並不是一個問題。單元測試可以大大地減少程序運行時才能發現的問題,這通常可以節省「用戶反饋」->「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
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。