嗯 最近比較休閑了,先泡一杯碧螺春定定神,然后開始總結MVC與MVP的項目運用中的優(yōu)缺點,剛開始用MVP這個架構開始項目搭建的時候可謂是小心翼翼呀,也是激動萬分的;使用之后覺得也就那么回事兒,面向?qū)ο蟮木幊倘f變不離其宗,只有掌握了扎實的編程基礎,一切都好說;
首先簡單闡述一下MVC;
MVC分為:Model(數(shù)據(jù)抽象)、View(視圖)、Controller(控制器)的三層架構。接下來我們分別來一一解析每一層所對應的職責分別是什么。
View層:對應的則是Android中的layout文件夾中的xml文件,在啟動Activity/Fragment的時候,都會加載一個R.layout.xxx的布局文件,使得在視圖中顯示出我們在xml中定義好的視圖。
Controller層:對應的則是Activity/Fragment。當Activity/Fragment加載了layout文件后,我們需要在Activity/Fragment中findViewById(int)去尋找到相對應的view,并對找到的view設置相應的屬性以及監(jiān)聽器。而在設置view的屬性之前,我們很有可能會先到model中請求一次數(shù)據(jù),當數(shù)據(jù)回調(diào)回來后controller就會去更新view了。
Model層:對應的則是一些DataSource以及DataBean的相關對象,這里的DataSource指的是數(shù)據(jù)的來源。一般數(shù)據(jù)的來源有2個主要的地方,一個是sqlite,一個是webservice,而我們習慣于將這兩種數(shù)據(jù)的來源封裝在一個repository中,對于調(diào)用者而言只需要調(diào)用repository中的一個獲取接口來獲取數(shù)據(jù),但是這個數(shù)據(jù)是從內(nèi)存中還是sqlite還是webservice來,我們都不得而知,從保護了調(diào)用實現(xiàn)的邏輯,分解相關的實現(xiàn),達到調(diào)用者的極度簡單與簡潔,且在單元測試中測試接口也是非常方便的。
首先是View:Activity_view.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_hello_mvc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Hello MVC" />
</FrameLayout>
接下來是Controller:ControllerActivity.java
// Controller
public class ControllerActivity extends Activity {
private Button mBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.Activity_view);
// 在此處,controller調(diào)用并訪問了view
mBtn = (Button) findViewById(R.id.btn_hello_mvc);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 對于這個 OnClickListener,是屬于view的,它是view的監(jiān)聽器
// 在這里,view直接訪問了model
String btnClickData = ModelDataSource.ins().getBtnClickData();
Toast.makeText(ControllerActivity.this, btnClickData, Toast.LENGTH_SHORT).show();
}
});
// 在此處controller調(diào)用了model
String btnText = ModelDataSource.ins().getBtnText();
// 在此處controller設置了view的屬性
mBtn.setText(btnText);
}
}
最后則是Model:ModelDataSource.java
// Model
public class ModelDataSource {
private static ModelDataSource mInstance = null;
public static ModelDataSource ins() {
if (mInstance == null) {
synchronized (ModelDataSource.class) {
if (mInstance == null) {
mInstance = new ModelDataSource();
}
}
}
return mInstance;
}
private ModelDataSource() {
}
public String getBtnText() {
// 在這里,
// 我們可以去數(shù)據(jù)庫中查找數(shù)據(jù),
// 也可以去網(wǎng)絡中獲取數(shù)據(jù)
return "I am from ModelDataSource";
}
public String getBtnClickData() {
// 在這里,
// 我們可以去數(shù)據(jù)庫中查找數(shù)據(jù),
// 也可以去網(wǎng)絡中獲取數(shù)據(jù)
return "Hello MVC!";
}
}
model層很多人會理解為是普通的javabean以及我的大學老師也是這么和我說的,但是我并不這么認為,我不認為model只是很簡單的一個數(shù)據(jù)結構定義,更多的它應該包含大量的數(shù)據(jù)處理和運算的邏輯,例如從數(shù)據(jù)庫中采集數(shù)據(jù)的操作或者通過網(wǎng)絡請求或者通過NetStream的方法來獲取到二進制的數(shù)據(jù),接著將這些二進制轉換為我們設定好的javabean也就是我們定義好的抽象數(shù)據(jù)模型,然后該對象進行傳遞以及顯示到視圖ui上。
17年之前項目用MVC這種性質(zhì)的項目結構是比較多的,M層封裝網(wǎng)絡請求的數(shù)據(jù),在C層去調(diào)用數(shù)據(jù)然后呈現(xiàn)出來;
優(yōu)點:Android開發(fā)中默認使用的框架,易于上手,能在不需要考慮太多需求的情況下快速開發(fā)一些小型demo功能app。
缺點:隨著業(yè)務的擴展controller會變的越來越臃腫和復雜,大大增加了開發(fā)人員的維護成本以及交接成本,使得后期工作難以展開,且隨著邏輯的復雜變化以及時間的推移會出現(xiàn)連開發(fā)人員自身都對當前代碼邏輯的復雜造成錯誤的理解。
MVP介紹:
從上圖中我們可以很清晰的看到MVP與MVC中的區(qū)別:
從Controller變成了Presenter
去除View和Model之間的調(diào)用關系,從而徹底的分離了Model和View之間的關聯(lián)與耦合
在MVP的架構中,有一個非常大的特點就是view和model之間的通信必須是通過presenter的傳遞,也正是因為這種隔離的關系,使得視圖和數(shù)據(jù)之間的關系變得完全分離。
還是老規(guī)矩,我們分別來介紹一下MVP架構中的:Model(數(shù)據(jù)模型)、View(視圖)、Presenter(主持者)他們?nèi)叩穆氊熞约跋嗷ブg的關系到底是如何運作的。
View層:視圖層,它所對應的不只是layout中的xml文件還包括了Activity/Fragment作為視圖的顯示。這樣做是擴大了View層的職責所在,View不僅是設置ui的顯示和屬性并且還包括了生命周期的回調(diào)。
Presenter層:主持者層,它相當于是Controller中的業(yè)務邏輯部分,它主要是負責view和model層之間的通信,及時的響應view層的請求并主動的調(diào)用model層的數(shù)據(jù)獲取,并且將獲取到的數(shù)據(jù)結果返回給view層中。presenter是另外新建立一個class,并且讓view從創(chuàng)建的時候就持有一個presenter的實例,當view發(fā)生某些請求響應或者生命周期發(fā)生變化,則會迅速的向presenter發(fā)起請求,讓presenter做出響應的處理,比如:刷新數(shù)據(jù)、清除數(shù)據(jù)防止泄露等。
Model層:此處的數(shù)據(jù)抽象層model和MVC中的model層是一樣的,這里就不做更多的敘述。
view和presenter兩者之間的通信并不是想怎么調(diào)用就可以怎么調(diào)用的,他們之間有著一個標準的協(xié)議,就是在兩者之間定義通用接口IContract,在這個interfac中定義了view層中要暴露的接口也定義了presenter層中需要暴露給view的接口,其目的是利用接口的方式將兩者進行隔離,兩者之間誰都不認識誰的實現(xiàn),達到面向接口編程的目的:
簡單的代碼邏輯:
// Contract
public interface IContract {
interface View {
void updateBtnText(String s);
void showToast(String s);
}
interface Presenter {
/**
* 調(diào)用該方法表示presenter被激活了
*/
void start();
void loadClickString();
/**
* 調(diào)用此方法表示presenter要結束了
* 其目的是為了接觸相互持有導致的內(nèi)存泄露
*/
void destroy();
}
}
View層ViewActivity.java:
// View
public class ViewActivity extends Activity implements IContract.View {
private Button mBtn;
private IContract.Presenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.Activity_view);
// 在最開始的時候構建presenter
mPresenter = new Presenter(this);
// View初始化
mBtn = (Button) findViewById(R.id.btn_hello_mvp);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.loadClickString();
}
});
}
@Override
protected void onStart() {
super.onStart();
mPresenter.start();
}
@Override
protected void onDestroy() {
if (mPresenter != null) {
mPresenter.destroy();
mPresenter = null;
}
super.onDestroy();
}
@Override
public void updateBtnText(String s) {
mBtn.setText(s);
}
@Override
public void showToast(String s) {
Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
}
}
Presenter層Presenter.java:
// Presenter
class Presenter implements IContract.Presenter {
private IContract.View mView;
Presenter(IContract.View view) {
mView = view;
}
@Override
public void start() {
String s = ModelDataSource.ins().getBtnText();
mView.updateBtnText(s);
}
@Override
public void loadClickString() {
String s = ModelDataSource.ins().getBtnClickData();
mView.showToast(s);
}
@Override
public void destroy() {
mView = null;
}
}
Model層ModelDataSource.java:
// Model
public class ModelDataSource {
private static ModelDataSource mInstance = null;
public static ModelDataSource ins() {
if (mInstance == null) {
synchronized (ModelDataSource.class) {
if (mInstance == null) {
mInstance = new ModelDataSource();
}
}
}
return mInstance;
}
public String getBtnText() {
// 在這里,
// 我們可以去數(shù)據(jù)庫中查找數(shù)據(jù),
// 也可以去網(wǎng)絡中獲取數(shù)據(jù)
return "I am from ModelDataSource";
}
public String getBtnClickData() {
// 在這里,
// 我們可以去數(shù)據(jù)庫中查找數(shù)據(jù),
// 也可以去網(wǎng)絡中獲取數(shù)據(jù)
return "Hello MVP!";
}
}
從代碼上看我們可以發(fā)現(xiàn)比起傳統(tǒng)的MVC從代碼數(shù)量上看似乎并沒有減少反而增加了不少的代碼和接口,從邏輯上看似乎有些暈乎。但事實并非如此,當我們理解了MVP后則會發(fā)現(xiàn)這種調(diào)用方式其實是非常清晰的,因為你根本無需去在乎到底是誰在調(diào)用你,你只需要知道:我要讓M做什么并且當M做完后我需要將M得出的結果告訴指定的V即可。同時在邏輯上的理解也是非常容易的。很顯然,從時序圖上我們可以看出其中的調(diào)用關系以及調(diào)用邏輯非常的清晰,并不會出現(xiàn)任何的跨道調(diào)用的現(xiàn)象,程序的執(zhí)行過程是非常有條理性。 因為有Presenter這個角色的存在使得view部分的代碼看上去是非常的清晰的,每一個方法都有它自己的主要傾向和職責所在,彼此之間并不會相互耦合。而Presenter中的代碼也是如此,每一個方法都只處理一件事,并不會做其他無相關的事情。
由此我們可以得出一個結論: 對于view來說:
我需要一個主持者,當出現(xiàn)view事件的響應或者生命周期的變化時,我需要告訴這位主持,我要做些什么。
我會提供一系列通用接口,以便于當主持完成我的請求后,調(diào)用相應的接口讓我明白這件事的結論是如何。
我所有的請求都發(fā)給主持,讓他幫我做決定,但是這件事的決定是如何做,我并不知道,但我需要結果。
對于presenter來說:
我只會接收到請求后找model尋求幫助,等model做完事情后通知我了,我在把結果傳遞給view。
我只知道指揮model做事、讓view顯示數(shù)據(jù),但我不干活。
我相當于一座橋,連接著view和model這兩座島,他們誰也不認識誰,想要通信必須要通過我,如果沒有我,他們兩永遠都不會認識。
優(yōu)點:
使用MVP可達到低耦合高內(nèi)聚并且盡可能的保證了開閉原則,非常符合當前的軟件工程;
由于模塊間的耦合很小,可做并行開發(fā),一邊開發(fā)View,一邊開發(fā)Model;
適合大部分的App,代碼邏輯清晰易懂,大大降低開發(fā)、維護和交接成本;
視圖和底層進行徹底的分離,View發(fā)生改變則只需要修改View部分代碼,底層數(shù)據(jù)實現(xiàn)發(fā)生改變則只需要修改底層Model的代碼。
缺點:對于很小的demo來說構建復雜和麻煩,不適合短期、小型且以后不在做任何維護的模塊開發(fā)。
總結:
雖然MVP是一款非常優(yōu)秀的架構,但是再優(yōu)秀的架構也還是會有缺陷的。在技術的世界中,沒有最完美的架構,只有最符合需求的架構,盡管再優(yōu)秀在完美的架構也還是會有很多不足之處的。在MVP中也是存在不少的不足之處,例如:在構建View和Presenter的時候,我們需要多寫大量的冗余接口,這無非是增加了額外的代碼量。還有就是假設我需要新增方法或者修改某個方法的參數(shù)、返回值等,則至少需要變動3個以上的文件,View,Presenter,以及IContract接口。這些都是MVP的不足之處。但是往往我們不能因為某些可以容忍的不足而放棄,也許放棄可以加快眼前的步伐,但對于未來將深陷到難以自拔了。