聊聊mvp架構

Android中傳統的沒使用架構的,或者使用mvc結構的,activity都承載了太多功能,既當了view層又當了control層

關於mvc本文不多討論了,記錄下工作中的使用mvp結構的例子

mvp結構從下上實際爲m->p-->v,即數據,控制,ui

先說下應用業務場景:

有關於門店shop的業務,有關於訂單order的業務

public class Shop
{

   private String     id;
   private String     icon;
   private String     name;
   private String     address;
   private List<Time> runningTimes;
   private int        bDRunning;
   private int        mTRunning;
   private int        eleRunning;
   private int        wxRunning;
   private int        wmOpen;

   public int getWxRunning()
   {
      return wxRunning;
   }

   public void setWxRunning(int wxRunning)
   {
      this.wxRunning = wxRunning;
   }

   public String getId()
   {
      return id;
   }

   public void setId(String id)
   {
      this.id = id;
   }

   public String getIcon()
   {
      return icon;
   }

   public void setIcon(String icon)
   {
      this.icon = icon;
   }

   public String getName()
   {
      return name;
   }

   public void setName(String name)
   {
      this.name = name;
   }

   public String getAddress()
   {
      return address;
   }

   public void setAddress(String address)
   {
      this.address = address;
   }

   public List<Time> getRunningTimes()
   {
      return runningTimes;
   }

   public void setRunningTimes(List<Time> runningTimes)
   {
      this.runningTimes = runningTimes;
   }

   public int getbDRunning()
   {
      return bDRunning;
   }

   public void setbDRunning(int bDRunning)
   {
      this.bDRunning = bDRunning;
   }

   public int getmTRunning()
   {
      return mTRunning;
   }

   public void setmTRunning(int mTRunning)
   {
      this.mTRunning = mTRunning;
   }

   public int getEleRunning()
   {
      return eleRunning;
   }

   public void setEleRunning(int eleRunning)
   {
      this.eleRunning = eleRunning;
   }

   public int getTRunning()
   {
      return mTRunning;
   }

   public void setTRunning(int TRunning)
   {
      mTRunning = TRunning;
   }

   public int getWmOpen()
   {
      return wmOpen;
   }

   public void setWmOpen(int wmOpen)
   {
      this.wmOpen = wmOpen;
   }

   @Override
   public String toString()
   {
      return "Shop{" +
            "id='" + id + '\'' +
            ", icon='" + icon + '\'' +
            ", name='" + name + '\'' +
            ", address='" + address + '\'' +
            ", runningTimes=" + runningTimes +
            ", bDRunning=" + bDRunning +
            ", mTRunning=" + mTRunning +
            ", eleRunning=" + eleRunning +
            ", wmOpen=" + wmOpen +
            '}';
   }
}
門店無非就是id,門店名,門店圖片,地址等等,只關注最簡單基本的數據就可以了

order也是id,支付時間,支付的錢,以及訂單的各個狀態(已支付,未支付,已確認,已關閉)

既然有兩大業務線,這裏以門店爲例來說明,門店的業務需求接口有(只關注與服務器交互有關的):

1 獲取門店數據

2 改變門店營業狀態

3 設置店鋪營業時間

4 獲取門店日結賬單

5 等等

以下提供的代碼看起來多的話,只聚焦一個業務需求,獲取門店的日結賬單,將會以這個小業務需求爲例子

顯然這是M層,我們可以定義一個接口IShopModel來代表門店這個業務需求

public interface IShopModel
{
   interface OnGetShopLisener
   {
      void onSuccess(Shop shop);
   }

   interface ShopTimeGettingListener
   {
      void onGetTime(List<Time> times);
   }

   interface ShopTimeSettingListener
   {
      void onSetting(boolean b);
   }

   interface ChangeRunningStatusListener
   {
      void onSuccess();

      void onFailed();
   }

   interface DayBillCallback
   {
      void onGetDayBill(DayBillResponse response);
   }


   interface NoticePubListener
   {

      void onPubSuccess();

      void onPubFailed();
   }

   interface NoticeGettingListener
   {
      void onGettingNotice(String notice);
   }


   /**
    * 獲取門店數據
    *
    * @param context
    * @param listener
    */
   VPayUIRequestV2 getShop(Context context,boolean isShow,OnGetShopLisener listener);

   /**
    * 改變門店營業狀態
    *
    * @param context
    * @param platform
    * @param actionType
    * @param listener
    */
   VPayUIRequestV2 changeRunningStatus(Context context, int platform, int actionType,
                               ChangeRunningStatusListener listener);


   /**
    * 獲取店鋪營業時間
    *
    * @param context
    * @param listener
    */
   VPayUIRequestV2 getShopRunningTime(Context context, ShopTimeGettingListener listener);


   /**
    * 設置店鋪營業時間
    *
    * @param context
    * @param listener
    */
   VPayUIRequestV2 setShopRunningTime(Context context, List<Time> mShopTimeList,
                              ShopTimeSettingListener listener);


   /**
    * 獲取門店日結賬單
    *
    * @param context
    * @param date
    * @param callback
    */
   VPayUIRequestV2 getShopDayBill(Context context, long date, DayBillCallback callback);


   /**
    * 發佈公告,由於該功能已屏蔽,暫不修改了
    *
    * @param context
    * @param notice
    * @param listener
    */
   void publishNOtice(Context context, String notice, NoticePubListener listener);

   /**
    * 獲取公告
    *
    * @param context
    * @param listener
    */
   void getNotice(Context context, NoticeGettingListener listener);
}

然後我們再定義一個門店的實現類ShopModleImpl implements IshopModel

該類會對所有的業務進行真正的實現

@Override
public VPayUIRequestV2 getShopDayBill(final Context context, long date, final DayBillCallback callback)
{
   DayBillRequest body = new DayBillRequest();
   body.setDate(date);
   String url ="";

   final VRequest<?> requestV2 = new VRequest<DayBillResponse>(url, body, context, false)
   {
      @Override
      public boolean onResponse(DayBillResponse response)
      {
         if (response.getCode() == HttpUtils.CODE_SUCCESS)
         {
            callback.onGetDayBill(response);
            return true;
         }
         else
         {
            Toast.makeText(context, response.getReason(), Toast.LENGTH_SHORT).show();
            return false;
         }
      }
   };
   requestV2.setShouldCache(false);
   requestV2.send();
   return requestV2;
}

上面是對獲取門店日結賬單的實現例子

上面有一行代碼

 callback.onGetDayBill(response);

這是對從服務器獲取數據對象後的回調,按照以往後者mvc結構,一般會回調到Activity中,但在mvc中,我們不直接給activity,而是給presenter層

同理,定義一個代表門店業務的Presenter

/**
 * 關於門店業務的總接口
 * 爲減少接口爆發,就實現IShopPresenter這一個總接口,只複寫與其業務有關的方法
 */
public interface IShopPresenter
{

   /**
    * 獲取門店數據
    */
   VPayUIRequestV2 getShop(Context context,boolean isShow);

   /**
    * 展示更改營業狀態的dialog
    *
    * @param platform,要更改的平臺,例如,0 美團,1百度,2餓了麼
    *
    * 注意,這是錯誤的mvp使用,與model無關的控制不必交給presenter    * 否則會導致類似V->P->V->P->V這種無用功
    */
   // void showDialogChangeStatus(int platform);

   /**
    * 改變某個平臺的營業狀態
    *
    * @param platform   平臺
    * @param actionType 0 關閉,1 營業
    */
   VPayUIRequestV2 changeRunningStatus(Context context, int platform, int actionType);


   /**
    * 獲取店鋪營業時間
    *
    * @param context
    */
   VPayUIRequestV2 getShopRunningTime(Context context);


   /**
    * 設置店鋪營業時間
    *
    * @param context
    */
   VPayUIRequestV2 setShopRunningTime(Context context, List<Time> mShopTimeList);


   VPayUIRequestV2 getShopDayBill(Context context, long date);

}

對於上面的接口,如果我們每個具體的presenter(例如獲取門店數據presenter,設置營業時間presenter,獲取門店日結賬單presenter)直接實現它,會每個present中有大量的空實現方法,因此這裏採用適配器模式,用一個基類BaseShopPresenter對其空實現,子類集成基類,複寫與自身相關的業務方法

public class BaseShopPresenter implements IShopPresenter
{
   @Override
   public VPayUIRequestV2 getShop(Context context,boolean isShow)
   {
      return null;
   }

   @Override
   public VPayUIRequestV2 changeRunningStatus(Context context, int platform, int actionType)
   {
      return null;
   }

   @Override
   public VPayUIRequestV2 getShopRunningTime(Context context)
   {
      return null;
   }

   @Override
   public VPayUIRequestV2 setShopRunningTime(Context context, List<Time> mShopTimeList)
   {
      return null;
   }

   @Override
   public VPayUIRequestV2 getShopDayBill(Context context, long date)
   {
      return null;
   }
}

這裏ShopDayDillPresenter只複寫與其相關的業務getShopDayBill()

public class ShopDayBillPresenter extends BaseShopPresenter
{
   public interface IShopDayBillView
   {
      void fillData(DayBillResponse response);
   }

   private IShopDayBillView mShopDayBillView;
   private IShopModel       mShopModel;

   public ShopDayBillPresenter(IShopDayBillView shopDayBillView)
   {
      mShopDayBillView = shopDayBillView;
      mShopModel = new ShopModleImpl();
   }

   @Override
   public VPayUIRequestV2 getShopDayBill(Context context, long date)
   {
      return mShopModel.getShopDayBill(context, date, new IShopModel.DayBillCallback()
      {
         @Override
         public void onGetDayBill(DayBillResponse response)
         {
            mShopDayBillView.fillData(response);
         }
      });
   }
}

關鍵一行的代碼

 mShopDayBillView.fillData(response);

可以看到由p控制view,填充數據

然後再看view層,怎麼進行調用

在DayBillActivity中,我們只需在需要數據的時候

/**
 * 獲取數據
 *
 * @param date 指定獲取時間
 */
public void initData(long date)
{
   IShopPresenter iShopPresenter = new ShopDayBillPresenter(this);
   mRequestV2 = iShopPresenter.getShopDayBill(mContext, date);
}

就可以了,當數據返回回來的時候,會自動進行填充

@Override
public void fillData(DayBillResponse response)
{
  //you can bind your data to view

}

這樣從一個業務請求開始,數據返回,數據填充,用mvp就表示完了.

在寫mvp時,本人也看了網上很多其他的寫法

最終用這個結尾吧

/**
 * 本項目於2016.04.15mvc改爲mvp結構
 * 在該結構中,modle層與mvc時一樣保持不變,增加presenter層,其負責具體的數據業務控制邏輯,
 * 注意,只與數據有關的才交給其處理
 * view層每一個具體的元素只負責其ui的初始化initView(Paramter pra...),
 * 數據的抽象初始化initData(Parameter pra...),以及需要model層幫助的交互
 * mvp結構中的要注意的地方:
 * 交互事件不一定要傳給Presenter * 這裏有個原則,就是如果這個事件需要Model層的幫助,
 * 那事件必須傳給Presenter * 否則請不要傳給Presenter,讓View自己處理,
 * 這樣纔不會導致類似V->P->V->P->V這種無用功
 * 特別是網上抄來抄去的showDialoghideDialog
 * public interface ISplashView {
 * void showProcessBar();
 * void hideProcessBar();
 * void showNetError();
 * void startNextActivity();
 * }
 * 這個簡單例子中可以看到View的接口方法就如此之多,
 * 按這種寫法在實際工程中View的接口必定會膨脹開來。
 * 一個接口中方法過多也必然違背了單一接口原則。
 * 以後更換View實現的過程是非常痛苦的,
 * 或者說幾乎更換不了View實現,那分層的意義就失去了。
 * 之所以會導致這種問題是因爲你的Presenter告訴View怎麼去渲染,
 * 而不是告訴View“直接用什麼去渲染。更通俗地說,
 * Presenter應該直接給View的加工後的數據。
 * View自己負責要怎麼去渲染。更多詳細可以查看:
 * http://blog.csdn.net/duo2005duo/article/details/50594757
 */




發佈了28 篇原創文章 · 獲贊 7 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章