一.為什么需要軟件設(shè)計(jì)模式?
我們先來定義什么是好的軟件架構(gòu):
- 軟件架構(gòu)上具有明確的分工,各個模塊的功能職責(zé)平衡分配能犯,且明確。
- 可測試性犬耻,通常良好的軟件架構(gòu)都具備良好的可測試性踩晶。
- 良好的易用性,維護(hù)成本低枕磁。
為什么需要模塊分工渡蜻?
良好的模塊分工,可以大大簡化我們對代碼的理解難度。雖然通過大量的開發(fā)工作茸苇,可以訓(xùn)練我們的大腦去分析越來越復(fù)雜的邏輯排苍,但是人總有極限,而且簡單的邏輯更容易理解学密、不容易出錯淘衙,所以,遵循單一職責(zé)原則腻暮,將復(fù)雜的業(yè)務(wù)邏輯分解彤守。
為什么需要良好的可測試性?
對于深知單元測試好處的開發(fā)者來說哭靖,這并不是一個問題具垫。單元測試可以大大地減少程序運(yùn)行時才能發(fā)現(xiàn)的問題,這通晨钋啵可以節(jié)省「用戶反饋」->「Bug修復(fù)」->「新版本發(fā)布」->「用戶安裝新版本」這個耗時長達(dá)一周以上的過程做修。所以霍狰,程序的可測試性對于程序的穩(wěn)定性是異常重要的抡草。
為什么需要良好的易用性?
毋庸置疑蔗坯,最好的代碼是還沒被寫出來的代碼康震。因此,越少的代碼宾濒,意味著越少的 bugs腿短。這也意味著盡量以最少的代碼實(shí)現(xiàn)相同的功能,并非意味著這個開發(fā)者懶惰绘梦,同時橘忱,也不能不看維護(hù)成本而盲目贊同一個看似聰明的方案。
二.什么是MVP架構(gòu)卸奉?
MVP是單詞Model View Presenter的首字母的縮寫钝诚,分別表示數(shù)據(jù)層、視圖層榄棵、發(fā)布層凝颇,它是MVC架構(gòu)的一種演變。作為一種新的模式疹鳄,MVP與MVC有著一個重大的區(qū)別:在MVP中View并不直接使用Model拧略,它們之間的通信是通過Presenter (MVC中的Controller)來進(jìn)行的,所有的交互都發(fā)生在Presenter內(nèi)部瘪弓,而在MVC中View會直接從Model中讀取數(shù)據(jù)而不是通過 Controller垫蛆。
首先我們先看下傳統(tǒng)的MVC架構(gòu)Model View Controller,我們把業(yè)務(wù)邏輯放到C層(ios的ViewController,android的Activity&Fragment),但是這里會引入另外一個問題袱饭,所有的邏輯都在C層弛随,不可避免的會造成C層非常復(fù)雜,如果項(xiàng)目越來越大宁赤,C層的代碼會更加臃腫舀透,維護(hù)起來也非常麻煩,而且也沒辦法==簡單的==做單元測試决左,試想做一個單元測試我們要加入多少邏輯代碼愕够?
綜上所述我們總結(jié)下,現(xiàn)有的MVC模式存在以下問題:
- 視圖與控制器間的過于緊密的連接
視圖與控制器是相互分離佛猛,但卻是聯(lián)系緊密的部件惑芭,視圖沒有控制器的存在,其應(yīng)用是很有限的继找,反之亦然遂跟,這樣就妨礙了他們的獨(dú)立重用。
- 視圖對模型數(shù)據(jù)的低效率訪問
依據(jù)模型操作接口的不同婴渡,視圖可能需要多次調(diào)用才能獲得足夠的顯示數(shù)據(jù)幻锁。對未變化數(shù)據(jù)的不必要的頻繁訪問,也將損害操作性能边臼。
- 不太友好的單元測試
特別是App上做單元測試的時候很多東西依賴與系統(tǒng)框架哄尔,沒法脫離用戶接口來測試這些邏輯單元。使用MVP對Presenter的測試--不需要使用自動化的測試工具柠并。 我們可以在Model和View都沒有完成時候岭接,就可以通過編寫Mock Object(即實(shí)現(xiàn)了Model和View的接口,但沒有具體的內(nèi)容的)來測試Presenter的邏輯臼予。
基于以上幾點(diǎn)問題鸣戴,就衍生了出了一些軟件設(shè)計(jì)模式如MVP,MVVW粘拾。為什么是MVP窄锅?我們先看下面這張圖
- MVP分離了view和model層,Presenter層充當(dāng)了橋梁的角色半哟,View層只負(fù)責(zé)更新界面即可酬滤,這里的View我們要明白只是一個viewinterface,它是視圖的接口寓涨,這樣我們在做單元測試的時候可以非常方便編寫Presenter層代碼盯串。關(guān)于mvp的代碼測試,我們可以參考google給出的代碼戒良,google現(xiàn)在也在推行mvp体捏,為此google發(fā)布了一些案例,大家可參考這里android-architecture
mvp-clean.png
- 厚重的Controller層代碼也得到了釋放,之前我們開發(fā)的時候會對UIViewController几缭、Activity河泳、Fragment編寫很多的業(yè)務(wù)邏輯,盡管大家會將Service層做分離年栓,如net層拆挥,DB層等,但還是無法避免類似的問題某抓,activity uicontroller無法重復(fù)利用是非常難以忍受的纸兔。
- 有一點(diǎn)還需要注意,presenter是雙向綁定的關(guān)系否副,因此汉矿,在設(shè)計(jì)的時候就要注意接口和抽象的使用,盡可能的降低代碼的耦合度备禀,這也是mvp的宗旨洲拇。
so,轉(zhuǎn)向mvp吧曲尸!我們先看下MVP幾個單詞的意思赋续,以下是我個人的理解:
- View: 是顯示數(shù)據(jù)(model)并且將用戶指令(events)傳送到presenter以便作用于那些數(shù)據(jù)的一個接口。View通常含有Presenter的引用队腐。在Android開發(fā)中通常將Activity或者Fragment作為View層蚕捉。
- Model: 對于Model層也是數(shù)據(jù)層。它區(qū)別于MVC架構(gòu)中的Model柴淘,在這里不僅僅只是數(shù)據(jù)模型。在MVP架構(gòu)中Model它負(fù)責(zé)對數(shù)據(jù)的存取操作秘通,例如對數(shù)據(jù)庫的讀寫为严,網(wǎng)絡(luò)的數(shù)據(jù)的請求等。
- Presenter:對于Presenter層他是連接View層與Model層的橋梁并對業(yè)務(wù)邏輯進(jìn)行處理肺稀。在MVP架構(gòu)中Model與View無法直接進(jìn)行交互第股。所以在Presenter層它會從Model層獲得所需要的數(shù)據(jù),進(jìn)行一些適當(dāng)?shù)奶幚砗蠼挥蒝iew層進(jìn)行顯示话原。這樣通過Presenter將View與Model進(jìn)行隔離夕吻,使得View和Model之間不存在耦合,同時也將業(yè)務(wù)邏輯從View中抽離繁仁。
三.實(shí)例
接下來我們看一個使用的用例吧涉馅,這個demo相對來說非常簡單,下面是項(xiàng)目的架構(gòu)黄虱,一個Activity稚矿,一個Fragment,Data層主要負(fù)責(zé)獲取App已安裝的應(yīng)用列表,AppListPresenter負(fù)責(zé)業(yè)務(wù)邏輯處理
我們先看下presenter晤揣,viewinterface的結(jié)構(gòu)
!
- AppListFragment的代碼
public class AppListFragment extends Fragment implements AppViewInterface {
private Presenter presenter;
private List<PackageInfo> packageInfoList = new ArrayList<>();
private RecyclerView recyclerView;
private MyAppListRecyclerViewAdapter myAppListRecyclerViewAdapter;
@Override
public void showAppList(List<PackageInfo> packageInfos) {
if (packageInfos.isEmpty())
return;
packageInfoList.clear();
packageInfoList.addAll(packageInfos);
myAppListRecyclerViewAdapter.notifyDataSetChanged();
}
@Override
public void setPresenter(Presenter presenter) {
this.presenter = presenter;
}
}
代碼比較容易理解桥爽,AppListFragment實(shí)現(xiàn)了AppViewInterface接口,我們需要在Activity中把AppListPresenter和AppViewInterface雙向綁定昧识。
- 接下來看下AppListPresenter層的代碼钠四,這里只列出了幾個關(guān)鍵方法
public class AppListPresenter implements Presenter, LoaderManager.LoaderCallbacks<List<PackageInfo>>{
private AppViewInterface viewInterface;
private AppClassLoader appClassLoader;
private LoaderManager loaderManager;
private final int id = 0;
public AppListPresenter(AppViewInterface viewInterface, AppClassLoader appClassLoader,
LoaderManager loaderManager) {
this.viewInterface = viewInterface;
this.appClassLoader = appClassLoader;
this.loaderManager = loaderManager;
viewInterface.setPresenter(this);
}
@Override
public void loadInstallApps() {
//通過loadmanager提供的方法獲取安裝的應(yīng)用列表
loaderManager.initLoader(id, null, this);
}
@Override
public void destory() {
loaderManager.destroyLoader(id);
}
@Override
public void onLoadFinished(Loader<List<PackageInfo>> loader, List<PackageInfo> data) {
//獲取到已安裝的應(yīng)用列表,調(diào)用AppViewInterface的showAppList方法
viewInterface.showAppList(data);
}
@Override
public void launchApp(PackageInfo packageInfo) {
Intent intent = appClassLoader.queryLaunchIntent(packageInfo);
if (intent != null)
appClassLoader.getContext().startActivity(intent);
else
Toast.makeText(appClassLoader.getContext(), "Can not start the app", Toast.LENGTH_SHORT).show();
}
}
關(guān)鍵方法是loadInstallApps跪楞,這個方法在MainActivity的onCreate中調(diào)用
private Presenter appListPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
AppListFragment appListFragment = AppListFragment.newInstance();
fragmentTransaction.add(R.id.fm, appListFragment);
fragmentTransaction.commit();
appListPresenter = new AppListPresenter(appListFragment, new AppClassLoader(getApplicationContext()),
getSupportLoaderManager());
//調(diào)用loadInstallApps
appListPresenter.loadInstallApps();
}
首先形导,我們獲取一個AppListFragment的實(shí)例,在AppListPresenter構(gòu)造函數(shù)里面我們傳入AppViewInterface习霹,同時在AppPresenter的構(gòu)造函數(shù)中又將presenter注入到了AppViewInerface里面朵耕,這樣就實(shí)現(xiàn)了Presenter和ViewInerface雙向綁定,之后調(diào)用AppPresenter的loadInstallApps方法淋叶,在onLoadFinished回調(diào)里面又調(diào)用了AppViewInterface的showApps方法阎曹,這樣數(shù)據(jù)就顯示在界面。整個Activity和Fragment的代碼精簡了很多煞檩。
四.缺點(diǎn)
由于對視圖的渲染放在了Presenter中处嫌,所以視圖和Presenter的交互會過于頻繁。還有一點(diǎn)需要明白斟湃,如果Presenter過多地渲染了視圖熏迹,往往會使得它與特定的視圖的聯(lián)系過于緊密。一旦視圖需要變更凝赛,那么Presenter也需要變更了