標籤:
最近經常看到各種介紹MVP模式的部落格的,之前寫過不少的Android應用,在做那些應用的時候,都是要求快速完成,所以從開始設計到寫代碼就一直考慮著重用。以前寫的項目基本都是不斷重構項目,將項目代碼變得更加精簡,提高代碼之間的複用性。但是代碼並沒有特別地注重按照MVC模式或者是MVP模式來,更多的是直接考慮模組化,重用,精簡。所以看了MVP模式後,決定去總結一下自己代碼中的問題並最佳化,算是對自己之前寫的代碼的回顧。
MVP架構
MVP架構是目前在Android流行起來的架構,它非常適合用於Android開發上面。我最早接觸MVP模式是在一本敏捷開發的書上。MVP分別指代M(model),V(View),P(Presenter):
- M(Model):表示資料模型,以及相關的資料結構。
- V(View):表示視圖,主要是指UI介面相關的那些東西。在Android裡面比如說layout的xml檔案,在MVP中,很多時候Activity/Fragment也是被看做View。
- P(Presenter):可以直接理解為視圖與模型的中間紐帶。
在MVP模式中,Model和View不直接進行關聯,而是通過Presenter來進行關聯的。往往實現的方式是增加一個IModel介面和IView介面,用Model和View分別實現那兩個介面,Presenter裡面儲存IModel和IView介面類型的成員變數。
程式碼範例
下面舉一個關於Android使用者資訊的MVP模式例子。
public interface IUserView{void setName(String name);}
使用者的JavaBean—User:
class User{ private String name; private String id; public String getName(){ return name; } public String getId(){ return id; } public void setName(String name){ this.name = name; } public void setId(String id){ this.id = id; }}
使用者模型介面
public interface IUserModel{void saveUser(User user);User load();}
真正模型
public class UserModel implements IUserModel{ void saveUser(User user){ // 儲存使用者資訊,儲存在本機資料庫中,或者xml裡面 } User loadUser(){ // 從網路中,或者從本機快取中讀取使用者資訊 return null; //未實現 }}
Presenter:
public class UserPresenter{ private IUserView view; private IUserModel userModel; public UserPresenter(IUserView view){ this.view = view; userModel = new UserModel(); } public void loadUser(){ // 此處可能是非同步load User user = userModel.loadUser(); view.setName(user.getName()); }}
把Activity作為View來看待
public class UserActivity extends Activity implements IUserView{ TextView tvName; //名稱對應的TextView UserPresenter userPresenter ; public void onCreate(Bundle bundle){ ... userPresenter = new UserPresenter(this); // userPresenter.loadUser(); 可以有先初始化 } void setName(String name){ tvName.setText(name); //如果presenter的loadUser是非同步線程,這裡可以通過tvName.post來運行在UI線程。 } void reloadUserData(){ //該函數可以某個按鈕的onClick事件裡面調用 userPresenter.loadUser(); // 可以是從網路中載入資料 }}
上面代碼中Activity不再與Model直接關聯,而是通過Presenter來間接關聯Model。並且當Model的資料變化了的時候,Presenter能夠通知View。上面的例子是View需要變化了,請求Presenter擷取資料。
分析
MVP與MVC最大的不同就是View和Model不再直接關聯。很多Android的MVC模式都是直接將Activity看作Control,這會導致整個Activity非常臃腫,因為它既然進行UI互動,還需要加上Control這部分的功能。另外即使另外建立一個Control,而把Activity只當做View的功能,如果Activity還是直接跟Model直接關聯的話,因為跟Model直接關聯,還是會在Activity增加很多操作。而使用P作為Model和View的紐帶,P可以先對模型資料進行一些處理,然後再顯示到View。另外一方面對於View的一些請求,Presenter也可以進行一些處理再去請求Model。
上面是關於代碼方面的優勢,其實通過分隔開Model和View,也是將各個模組進行瞭解耦。另外一方面通過增加IUserView和IUserModel,這樣每個部分進行單元測試也更加方便了,比如可以直接實現IUserView來類比測試Presenter。
個人覺得MVP模式適合Android應用,一個很大的原因就是Activity這種組件的存在,UI互動完全放在了Activity裡面,這導致很多時候Activity會在一不注意間就變得臃腫。View過於龐大。當然還有很多跟Android相關的優勢,比如說能夠更好地避免Activity的記憶體流失(Presenter直接引用View,而不是Activity的時候)。其實通過增加Presenter,一定程度上也增加了Presenter的複用,很多人說View和Presenter是一一對應的,但是我覺得如果不一一對應,比如說一個Activity裡麵包含多個Presenter,將Presenter細化,那樣Presenter複用的可能性也就越高了,同時也避免了Presenter過於臃腫。
一個模式最佳化過程
平時自己寫代碼的時候,其實還真的是比較少那麼明顯地使用這種MVP模式,或者說是MVC模式。更多的是模組化,類層次化(物件導向),面向切面,並且堅持代碼精簡,複用的原則。下面介紹一個將平時的項目簡化後的模式,並且通過考慮最佳化結構將模式轉變成MVP的過程。先看看下面這種類型結構:
先模組化將程式分為Fragment/Activity部分,Adapter部分,模型網路操作部分,通過將相關類進行層次化來減少類的臃腫,另外通過AOP編程的方式將一些內容從子父類中提取出來,作為一個單獨的切面。這種面向切面能夠提高代碼的複用性。
上面的類型圖裡面也有一個不合理的例子,正如上面的注釋中所說的將網路操作作為模型的一部分,而不是再添加一個介面,會導致網路操作一旦修改,會影響到Model的子類。
下面看看如何對上面的結構進行轉換。上面這種結構更多的是MVC的方式,而且是將V和C基本合在一起了,內聚在一起能夠更方面UI互動,但是也會導致臃腫。上面的圖中發現只有BaseFragment單向地去關聯Model,這樣勢必會導致Fragment增加更多的代碼來控制,因為在Android中進行網路連接是一定要在非UI線程操作的,如果Model沒有關連Fragment,必然需要將一個非同步作業放到Fragment,等待Model操作完成了後,Fragment來更新Fragment的內容。如果增加一個介面,然後Fragment實現,並且用Model引用它,那麼將能夠減少Fragment的操作,而且能夠更好地去對Model進行單元測試。如果增加一個Presenter,由Presenter與Fragment相互關聯,然後Presenter也與Model相互關聯,那麼Fragment將會大大簡化,更加專註一些其他的UI互動。增加Presenter整個架構就變成了MVP模式了。重構後的類型圖如下:
總結
模式總是堅持著複用,模組間低耦合,模組內高內聚等等原則來進行的,設計模式中就有六大原則: 單一職責原則,開閉原則,依賴倒轉原則,迪米特法則,裡氏替換原則,組合彙總原則。好的模式能夠讓人在閱讀的時候能夠很好地理解代碼,在對程式進行修改的時候能夠快速簡潔,並且不對原有代碼結構破壞。
關於Android MVP模式的思考