MVP架構模型的一些延伸筆記

零、前言
Activity是什麼?
    Activity是上帝類。不考慮架構的話,Activity類再開發中會愈來愈多堆積如山。由於在Android中,容許View和其餘線程共存於Activity。這樣就形成Activity中同時存在業務邏輯和UI邏輯。增長測試和維護成本,這樣腫脹的Activity,還會引發其餘問題,好比A和F的生命週期變得複雜很差控制或是數據綁定問題。
Activity負擔如此的重,既要初始化控件又要進行邏輯代碼的展現,至關於Activity充當了Controller和部分Model的角色,這樣違背了 單一職責的原則!因此在MVC基礎上衍生了MVP!
 
1、MVP是什麼?
 
    MVP架構師google開源的一個設計模式,目的是爲了將代碼更加優雅清晰的呈現出來。爲了更好的細分視圖層與模型層的功能,讓View專一於數據的可視化以及與用戶的節目交互,同時,讓Model層只關心數據的業務處理,基於MVC的理念產生了MVP模型。經過接口實現和接口調用的方式,使得用更簡潔的代碼完成功能的建立。
 
2、MVP各個是什麼?
    M層:model層,進行處理處理,負責數據訪問。數據能夠是遠端Server API、本地數據庫、sharedPreference等;
    V層:View層,進行數據顯示與用戶交互,將數據傳遞給View層,經過View層顯示數據。同時,View層的點擊事件等在這裏進行操做,但設計業務的數據層交還給model層處理;
   ps:View interface,View層實現該接口,經過View interface與Presenter進行交互,下降耦合,方便進行單元測試;
     P層:Presenter層,鏈接(適配)View層和Model層的橋樑。經過p層鏈接M層和V層進行通訊。M層獲取到數據後交給P層,由P層傳遞給View層顯示;同理,view層各種事件處理經過P層去通知M層,讓其進行處理。
 
View是UI線程;Presenter是View和Model之間的適配器。UseCase or Domain在Model層中,負責從實體獲取或載入數據,規則:
ps:高層接口不能,不該該,而且必須不該該嘗試去了解底層接口的細節,這就是面向對象的抽象方式,而且細節應該隱藏掉。
 
依賴規則?
Uncle Bob的「The Clean Architecture」描述了依賴的規則是什麼。
同心圓將軟件劃分爲不一樣的區域,通常的,隨着層級的深刻,軟件的等級也就越高。外圓是實現機制,內圓是核心策略。
​Entities:
  • 能夠是一個持有方法函數的對象
  • 能夠是一組數據結構或是方法函數
  • 並不重要,能在項目中被不一樣應用程序使用便可
 
UseCases
  • ​包含特定於應用程序的業務規則
  • 精心編排流入Entity or 從Entity 流出的數據
  • 智慧Entity直接使用項目範圍內的業務規則,從而實現UseCase目標
 
Presenters、Controllers
  • 將UseCases和Entity中的數據轉換成格式最方便的數據
  • 外部系統,如數據庫或是網頁可以方便的使用這些數據
  • 徹底包含GUI的MVC架構
 
External Interfaces,UI,DB
  • ​全部的細節邏輯
  • 數據庫細節、Web框架細節等
 
3、MVP的優勢 
    一、下降耦合度,隱藏數據邏輯,減輕Activity 或是Fragment的壓力,讓其更多的只關注處理生命週期任務,使得代碼更加簡潔明瞭
         ps:View層的實現Activity或是fragment基本上只處理findViewById,setListener之類的代碼,Ui邏輯都經過View接口的實現,和P層對象的引用
    二、模塊職責劃分明顯,視圖邏輯和業務邏輯分別抽象到V層和P層的接口中去,提升代碼可閱讀性,複用度較高,靈活性高
    三、方便測試驅動開發
         ps:因爲業務邏輯都在Presenter裏,能夠寫一個PresenterTest實現類繼承Presenter的接口,就能進行單元測試了
    四、減小Activity的內存泄露問題
        ps:採用傳統的開發模式,不少異步任務和對UI的操做都是在Activity的進行的,好比下載一個圖片,下載成功的回調裏把圖片加載到Activity的ImageView裏面,異步任務保留着對Activity的引用。這樣即便Activity已經被銷燬,這些異步任務可能仍然保留着對Activity實例的引用,系統也就沒法回收這個Activity的實例,結果就形成了Activity Leak ,Activity對象每每是在堆裏佔據最多內存的,因此係統優先回收級高,若是有AL現象,很容易形成內存不足
    使用MVP模型,只要在onDestroy()中分離異步任務對Activity的引用就能更好的避免Activity leak
 
說了這麼多優勢 ,那MVP的缺陷呢?
     最明顯的建立一個Activity須要配合建立多個接口類和實現類,每一個操做都須要經過接口回調的方式進行,雖然邏輯清晰代碼,同時也形成了類的增多和代碼量的加大。
 
解決方案:
    利用泛型封裝一下MVP的base類,經過泛型所提供的類型去實例化View層和Presenter層,在繼承封裝好的基類中快速的使用MVP模式。注意的是經過泛型約束後,在繼承的時候須要填寫多個泛型值。
 
4、MVP設計思路
    中心思想:面向接口編程,調用層使用接口對象,去調用接口方法;實現層去實現接口方法,並在調用層,實例化出來。
     
  實例(1):  好比咱們要作一個加載圖片的抽象方法也就是下面的這片代碼:
    public interface ImageLoaderInterface<T extends View> extends Serializable {
    void displayImage(Context context, String path, T imageView);
    void displayImage(Context context, @DrawableRes Integer resId, T imageView);
    T createImageView(Context context);
   }
 
    使用的時候就是直接去使用它的方法,好比咱們要在adapter中使用他, 那麼咱們不須要在adapter中去實例化他,而是直接使用這個接口對象,在使用的地方調用接口的方法:
  public ImageLoaderInterface imageLoaderInterface; public void showPic(ViewHolder holder) {
    imageLoaderInterface.displayImage(context, addPicRes, holder.iv_pic);
  }
    
    在其餘類中去寫它的實現類,在adapter的初始化中把這個實現類去傳遞給他:
  public abstract class ImageLoader implements ImageLoaderInterface<ImageView> {
    @Override
    public ImageView createImageView(Context context) {
      return new ImageView(context);
    }
   }
     public class Loader extends ImageLoader {
      @Override
      public void displayImage(Context context, String path, ImageView imageView) {
        Glide.with(context).load(path).into(imageView);
      }
      @Override
      public void displayImage(Context context, @DrawableRes Integer resId, ImageView imageView) {
        imageView.setImageResource(resId);
      }
    }
      adapter中寫set方法,讓外部調用 adapter.setImageLoaderInterface(new Loader());
 
        mvp架構中,咱們要把m層的接口方法抽象出來,p層的接口方法抽象出來,v層的接口方法抽象出來,同時分別寫3個接口的實現類,(v層通常是activity或者fragment繼承view層的接口),v層持有p的接口對象,p層持有v層和m層的接口對象,m層爲p層的提供數據,這時也就造成了mvp架構,三者之間經過p層相互鏈接。
——————————————————————————————————————————————
再看一個簡單的實例:
實例(2):首先給Activity的View層定義一個接口類:
/** * Interface classes for the Top view */
 public interface TopView { 
    /** * Initialize the view. * * e.g. the facade-pattern method for handling all Actionbar settings */
     void initViews(); 
  
  /** * Open {@link DatePickerDialog} */ 
    void openDatePickerDialog(); 
  
  /** * Start ListActivity */
     void startListActivity(); 
}
    下面是 View層在Activity中的實現,並在Activity中實例化P層對象:
 
  • TopActivity只是負責處理事件監聽或者展現每一個視圖組件
  • 全部的業務邏輯必須委託給Presenter類
  • 在MVP中,View和Presenter是一一對應的在這裏(在MVVM中是一對多的關係)
 
public class TopActivity extends Activity implements TopView { 
        // here we use ButterKnife to inject views /** * Calendar Title */ 
    @Bind(R.id.calendar_title) 
    TextView mCalendarTitle; 
        private TopPresenter mTopPresenter; //P層對象
        @Override 
        protected void onCreate(Bundle savedInstanceState) { 
            super.onCreate(savedInstanceState); setContentView(R.layout.activity_top); 
                ButterKnife.bind(this); 
        // Save TopPresenter instance in a meber variable field 
            mTopPresenter = new TopPresenter(); 
            mTopPresenter.onCreate(this); 
        } 
        /* * Overrides method from the {@link TopView} interfaces */ 
        @Override 
        public void initViews() { 
                // Actionbar settins 
                // set event listeners 
        } 
        @Override 
        public void openDatePickerDialog() {
         DatePickerFragment.newInstance().show(getSupportFragmentManager(), DatePickerFragment.TAG); 
        // do not write logic here... all logic must be passed to the Presenter
             mTopPresenter.updateCalendarDate(); 
        } 
        @Override 
        public void startListActivity() {
         startActivity(new Intent(this, ListActivity.class)); 
        } 
}
 
    接下來是Presenter類,也就是P層,是鏈接View和Model的適配橋樑。在這裏TopUseCase的saveCalendarDate()方法是對TopPresenter細節的隱藏。P層不須要關係數據結構或是業務邏輯,只須要在須要的時候能夠經過對象調用方法將數據和節目進行鏈接便可。所以能夠對TopUseCase便是Modle層進行單元測試,由於業務邏輯與視圖是分離的。
    public class TopPresenter { 
        @Nullable 
        private TopView mView;  //View層對象
        private TopUseCase mUseCase; //Model層對象
    public TopPresenter() { 
        mUseCase = new TopUseCase(); 
    } 
    public void onCreate(@NonNull TopView topView) {
         mView = topView; 
        // here you call View's implemented methods 
        mView.initViews(); 
    } 
    public void updateCalendarDate() { 
        // do not forget to return if view instances is null 
        if (mView == null) { 
        return; 
        } 
    // here logic comes 
        String dateToDisplay = mUseCase.getDateToDisplay(mContext.getResources());       
                      mView.updateCalendarDate(dateToDisplay); 
        // here you save date, and this logic is hidden in UseCase class 
        mUseCase.saveCalendarDate(); 
       } 
    }
 
在這裏推薦的測試類庫是http://robolectric.org/ ,進行部分功能的單元測試工做。
 
——————————————————————————————————————————————
實例(3):一個p層去持有多個model方案,只有Presenter層須要特殊處理,其餘同上方基礎例子:
  public class MoreModelPresenter implements MoreModelContract.Presenter {
   private MoreModelContract.OneModel oneModel;
  private MoreModelContract.TwoModel twoModel;
  private MoreModelContract.ThreeModel threeModel;
    private MoreModelContract.View view;
    public MoreModelPresenter(MoreModelContract.OneModel oneModel, MoreModelContract.TwoModel twoModel,
        MoreModelContract.ThreeModel threeModel, MoreModelContract.View view) {
    this.oneModel = oneModel;
    this.twoModel = twoModel;
    this.threeModel = threeModel;
    this.view = view;
    }
    @Override
    public void getData1() {
    view.showData1(oneModel.doDataOne());
    }  
     @Override
    public void getData2() {
     view.showData1(twoModel.doDataTwo());
    }
    @Override
    public void getData3() {
     view.showData1(threeModel.doDataThree());
    }
 }
 
單V多p多m架構
 public class MainActivity extends AppCompatActivity implements MorePresenterContract.View {
    private MorePresenterOne morPresenterOne;
    private MorePresenterTwo morePresenterTwo;
    @Override
   protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    morPresenterOne = new MorePresenterOne(MorePresenterModelOne.getInstance(), this);
    morePresenterTwo = new MorePresenterTwo(MorePresenterModelTwo.getInstance(), this);
    findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
         morPresenterOne.getData1();
    } });
    findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        morePresenterTwo.getData2();
    } }); }
      @Override
      public void showData1(String str) {
        Toast.makeText(this,str,Toast.LENGTH_SHORT).show();
    }
      @Override
      public void showData2(String str) {
        Toast.makeText(this,str,Toast.LENGTH_SHORT).show();
    }
   }
    //mvp接口類
  public interface MorePresenterContract {
      interface View { //顯示數據
        void showData1(String str);
        void showData2(String str);
    }
      interface PresenterOne { //通知model要獲取數據並把model返回的數據交給view層
         void getData1();
    }
     interface PresenterTwo { //通知model要獲取數據並把model返回的數據交給view層
        void getData2();
    }
     interface ModelOne { //獲取數據
        String doData1();
    }
      interface ModelTwo { //獲取數據
       String doData2();
    }
   }
    //第一個p
   public class MorePresenterOne implements MorePresenterContract.PresenterOne {
    private MorePresenterContract.ModelOne modelOne;
    private MorePresenterContract.View view;
    public MorePresenterOne(MorePresenterContract.ModelOne modelOne, MorePresenterContract.View view) {
      this.modelOne = modelOne;
      this.view = view;
    }
    @Override
    public void getData1() {
      view.showData1(modelOne.doData1());
    }
   }
    //第二個p
   public class MorePresenterTwo implements MorePresenterContract.PresenterTwo {
    private MorePresenterContract.ModelTwo modelTwo;
    private MorePresenterContract.View view;
    public MorePresenterTwo(MorePresenterContract.ModelTwo modelTwo, MorePresenterContract.View view) {
     this.modelTwo = modelTwo;
     this.view = view;
    }
    @Override
    public void getData2() {
      view.showData2(modelTwo.doData2());
    }
   }
    //model就是普通寫法
    public class MorePresenterModelOne implements MorePresenterContract.ModelOne {
       private static MorePresenterModelOne morePresenterModelOne;
      public static MorePresenterModelOne getInstance() {
        if (morePresenterModelOne == null) {
          morePresenterModelOne = new MorePresenterModelOne();
        }
        return morePresenterModelOne;
      }
      @Override
      public String doData1() {
         return "MorePresenterModelOne";
      }
     }
 
 
model模塊拆分,將大量複用的model單獨拆分出來,讓這個model能夠去被其餘model調用
  public interface ModelIncludeModeContract {
    interface View {
      //顯示數據
     void showData(String str);
    }
    interface Presenter {
      //通知model要獲取數據並把model返回的數據交給view層
    void getData();
    }
    interface Model {
     //獲取數據
    String doOtherDataOne();
    // String doOtherDataTwo();
    }
  }
     public class OneModelIncludeOther implements ModelIncludeModeContract.Model {
       private IOtherMode otherMode;
       private IOtherModeTwo otherModeTwo;
      //主model使用時去初始化其餘子model,讓主model能夠調用子model的方法
      public OneModelIncludeOther(IOtherMode otherMode, IOtherModeTwo otherModeTwo) {
         this.otherMode = otherMode; this.otherModeTwo = otherModeTwo;
       }
        @Override
      public String doOtherDataOne() {
        return otherMode.otherData();
       }
       @Override
       public String doOtherDataTwo() {
         return otherModeTwo.otherDataTwo();
      }
     }
     //第一個子model的接口類
     public interface IOtherMode { String otherData();
    }
    //第二個子model的接口類
      public interface IOtherModeTwo { String otherDataTwo(); }
 
 
5、MVC、MVP、MVVM?
    這些模式動機是同樣的:那就是如何避免複雜混亂的代碼邏輯,讓執行單元測試變得更容易,創造高質量的代碼及應用。
    MVVM是Model-View-ViewModel的縮寫。是由MVP模式與WPF結合的應用模式。
     
    MVVM架構:
    Model:表明你的基本業務邏輯
    View:顯示內容
    ViewModel:將前面二者聯繫在一塊兒的對象
        一個ViewModel接口提供了兩種東西:動做+數據;動做改變Model的下層(click listener,監聽文字改變的listener等),而數據則是Model的內容。ViewModel在改變內容以後通知binding framework內容發生了改變,而後framework自動更新和那些內容綁定的View。這樣能夠在沒有View的狀況下進行ViewModel的測試。
    
    MVVM的優勢 
    一、低耦合
    View能夠獨立於Model變化和修改,一個ViewModel能夠綁定到不一樣的View上,當View變化的時候Model並不受影響,反之也不會受影響。
    二、可重用性
    能夠把一些視圖邏輯放在ViewModel裏面,讓不少View重用這些相似的視圖邏輯
    三、獨立開發
    開發人員能夠專一於業務邏輯和數據的開發
    四、更好的測試性
    針對於ViewModel的測試將更容易於沒有界面的狀況
 
 

 

    (1)MVP與MVC一個重大區別:
    在MVP中View並不直接使用Model,也就是View層與Model層沒有任何直接鏈接,必須經過P層進行通訊進行,全部交互都發生在Presenter內部。
    而在MVC中View會從直接Model中讀取數據而不是經過Controller。View層能夠直接訪問Model層,從M層獲取數據,這樣就不可避免的涉及到一些業務邏輯的處理。在MVC模型裏,M層不依賴於View層,可是V層有時候確實依賴於M層的。在V層裏的業務數據處理也致使了不能更好的代碼複用。
    
     (2) MVVM和MVP的區別:
MVVM模式將MVP中的Presenter更名爲ViewModel,惟一的區別是,它採用了雙向綁定(data-binding);View的變更,自動反映在ViewModel上,反之亦然。開發時候不用處理接受不了事件和View更新的工做。
 
 
參考資料及連接文檔:
The Clean Architecture(譯者注:清晰架構。譯文) - Uncle Bob
這篇文章由Uncle Bob撰寫,描述了依賴規則的樣子和它們之間的組件是如何工做的。我從一開始談論的那張圖表的靈感就來源於他的文章,雖然這篇文章不是針對Android開發的,可是同往常同樣,字裏行間蘊藏着不少精闢的道理,因此,必讀。
Architecting Android…The clean way? (譯者注:Android中的清晰架構。譯文)- Fernando Cejas
我認爲這是在探索如何將MVP架構到Android開發專題中最著名,也是最受歡迎的博客。我也是從他那篇簡單易讀,書寫良好的博客中偶然發現「MVP」這個名詞的。他的示例代碼託管在Github上,以便那些想要將MVP架構運用到正式App上的Android開發者clone到。
Android Architecture(譯者注:Android架構)  - Thanos Karpouzis
一個在Android項目中運用MVC,MVP,MVVM的簡單指導。我從他的那篇普通卻不平凡的文章中學到了不少,尤爲是MVC,MVP和MVVM之間的不一樣。
Software Design patterns on Android English(Android開發中的軟件設計模式) - Pedro Vicente Gómez Sánchez
這是一個在Karumi工做的高級Android開發工程師所講的,他解釋了一些MVP架構中的設計模式(如,渲染模式,倉庫模式和命令模式)。若是你想深刻理解MVC或者MVP,那這就使你要找的。
M — Model in MVC, MVP, MVVC in Android(MVC,MVP,MVVC架構中Model層在Android中的定義) - Artem Zinnatullin
若是你不還了解Model層中的JSON與SQL,或者不能透徹理解Model層的圖像模型,這篇文章將帶你進一步理解什麼是Model層以及爲何Model層獨立於其餘層。其中「Model layer is solution」部分很好的解釋瞭如何經過面向接口的方式編寫測試。
再次感謝做者:
 
相關文章
相關標籤/搜索