導(dǎo)語:MVP開發(fā)模式可以幫助項目結(jié)構(gòu)解耦,但其龐大的方法數(shù)增加新啼,較為笨重設(shè)計對于手Q項目并不很適合伤靠。參考之前Web開發(fā)經(jīng)驗,提出以頁面結(jié)構(gòu)化的解耦方式組織代碼逾条。下面講講Lego在Android上一次小小嘗試
一琢岩,MVP簡介
MVC太過常見這里不啰嗦。實(shí)際應(yīng)用MVC當(dāng)中师脂,Activity占據(jù)打部分的工作担孔,View和Controller的身份分不清。而MVP則是一種設(shè)計模式專門優(yōu)化Activity / Fragment吃警。
先來看看MVP模式的核心思想:View不直接與Model交互
MVP 把 Activity 中的 UI 邏輯抽象成 View 接口攒磨,把業(yè)務(wù)邏輯抽象成 Presenter 接口,Model 類還是原來的 Model
在MVP設(shè)計模式中汤徽,
View:由Activity充當(dāng)娩缰,并且響應(yīng)生命周期
Model:還是原來的數(shù)據(jù)層,網(wǎng)絡(luò)谒府,緩存拼坎,解析等。
Presenter:作為View和Model的中間紐帶完疫,View不能直接對Model進(jìn)行操作泰鸡,必須經(jīng)過Presenter
View interface:需要View實(shí)現(xiàn)的接口,View通過View interface與Presenter進(jìn)行交互壳鹤,降低耦合
二盛龄,日跡MVP實(shí)戰(zhàn)應(yīng)用
【Mode層】我們直接忽略
【View Interface】首頁的View接口,抽離出view和presnter交互的接口芳誓。由Activity繼承實(shí)現(xiàn)(Now.java余舶,QQStoryMainActivity.java)
public interface IMyStoryListView {
? ? public void setData(MyStorys myStoryList, RecentStory recentStoryList);
? ? public void setSegmentData(String key, Object data,boolean needRefreshUi);
? ? /**
? ? * 更新數(shù)據(jù)后刷新界面走的回調(diào)
? ? * @param success
? ? * @param isManualPullRefresh
? ? */
? ? public void pullRefreshCompleted(boolean success,boolean isReqCompleted);
? ? public void launchNewVideoTakeActivity(boolean autoStart, boolean checkSo, int entranceType,String extra);
? ? public void setPlayVideoBtnDisplay(boolean display);
? ? public void showStartDownload();
? ? public void showDownloadCompleted(boolean success);
? ? public void storyPreLoadCompleted(String category, String uin);
? ? public void LoadMoreCompleted(boolean repositoryUpdated, boolean isEnd);
? ? public void showEmptyView(boolean display);
? ? public void requestDataCompleted();
? ? public void openMyStoryListView(boolean open);
}
【View】我們的Activity實(shí)現(xiàn)了View接口,并且實(shí)現(xiàn)生命周期
public class QQStoryMainAcitivty extends QQStoryBaseActivity implements IMyStoryListView {
? ? protected StoryHomePushYellowBarHandler mStoryHomePushYellowBarHandler = new StoryHomePushYellowBarHandler();
? ? protected MystoryListView mainListView;
? ? protected IMyStroyPresenter myStoryListPresenter;
? ? @Override
? ? protected boolean doOnCreate(Bundle savedInstanceState) {
? ? ? ? super.doOnCreate(savedInstanceState);
? ? ? ? mainListView = (MystoryListView) super.findViewById(R.id.qqstory_story_main_listview);
? ? ? ? //Presenter
? ? ? ? myStoryListPresenter = new StoryListPresenter(this);
? ? ? ? myStoryListPresenter.setIView(this);
? ? ? ? return true;
? ? }
? ? @Override
? ? public void onStartAutoRequestFromNet() {
? ? ? ? startTitleProgress();
? ? ? ? mainListView.pullToRefresh();
? ? ? ? mStoryHomePushYellowBarHandler.clearYellowBar();
? ? ? ? myStoryListPresenter.requestAllDataFromNet();
? ? }
? ? private void startTitleProgress(){
? ? ? ? // do more
? ? }
}
舉個例子锹淌,用戶下拉刷新一下匿值。觸發(fā)到Activity的onStartAutoRequestFromeNet。View邏輯在Activity赂摆。
業(yè)務(wù)邏輯則由Presnter的requestAllDataFromNet去實(shí)現(xiàn)挟憔。
【Presenter】具體的View->Model,Mode->View由這里實(shí)現(xiàn),其中View是有View接口抽象烟号,進(jìn)一步規(guī)范化View的邏輯绊谭。
必要是可以抽出Presenter接口(其實(shí)日跡這里沒有必要)
public class StoryListPresnter implements IMyStroyPresenter{
? ? protected IMyStoryListView mIView;
? ? protected FeedItem mFeedItem;
? ? protected ParallelStepExecutor mRequestNetDataExecutor;
? ? @Override
? ? public void onCreate(boolean needUpdateFromNet) {
? ? ? ? // 生命周期的邏輯處理
? ? ? ? mFeedItem = new FeedItem();
? ? }
? ? @Override
? ? public void setIView(IMyStoryListView IView) {
? ? ? ? // 設(shè)置View接口(目前實(shí)現(xiàn)的是Activity,但其實(shí)由其他Fragment汪拥,View實(shí)現(xiàn)都是可以的达传,這就是MVP的好處之一,解耦)
? ? ? ? mIView = IView;
? ? }
? ? public boolean requestAllDataFromNet() {
? ? ? ? mRequestNetDataExecutor.addStep(new GetUserSelfInfoStep(null))
? ? ? ? ? ? ? ? .addStep(new ReportWatchVideoListStep(StoryListPresenter.this))
? ? ? ? ? ? ? ? .addStep(new GetUserGuideInfoStep(StoryListPresenter.this))
? ? ? ? ? ? ? ? .onCompleted(new SimpleStepExector.CompletedHandler() {
? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? public void done(FeedItem item) {
? ? ? ? ? ? ? ? ? ? ? ? // 偽代碼
? ? ? ? ? ? ? ? ? ? ? ? mFeedItem = item;? ? ? ? ? ? ? ? ? ? ? // 處理Model層
? ? ? ? ? ? ? ? ? ? ? ? mIView.openMyStoryListView(mFeedItem);? // 根據(jù)View接口調(diào)用View更新
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }).run();
? ? }
}
MVP的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
解耦,絕對的趟大。不然抽這么多接口干嘛
模塊職責(zé)明確鹤树,層次清晰
Presenter可復(fù)用(在日跡的需求中,首頁和4Tab公用一個Presnter)
方便單元測試
避免Activity內(nèi)存泄露逊朽, Acitvity一身輕松
MVP的缺點(diǎn)也是非常明確的:
非常的笨重罕伯。一個View就對應(yīng)一個Presenter,輕業(yè)務(wù)一個Activity能解決的就不要解決
Presnter依然邏輯繁重叽讳。Acitivty輕松了追他,業(yè)務(wù)邏輯龐大的時候Presnter依然是大胖子。
代碼復(fù)雜度岛蚤,學(xué)習(xí)成本邑狸。這玩意不好理解,需要實(shí)戰(zhàn)中理解涤妒。
在手Q項目里单雾,MVP會激增很多方法數(shù)。
三她紫,Lego頁面結(jié)構(gòu)化
前面鋪墊這么多硅堆,終于到我要吹水的時候了。MVC贿讹,MVP渐逃,還有MVVM等MVX系列的設(shè)計模式,都是一種大而全的統(tǒng)一管理民褂。在項目結(jié)構(gòu)中最為關(guān)鍵其實(shí)是:分模塊茄菊!
看看某寶的首頁,頂部搜索欄赊堪,banner面殖,導(dǎo)航分類,搶購雹食,特價畜普,底部Tab。這是一個Activity的話群叶,你再怎么MVP,也是需要劃分模塊钝荡,然后分而治之街立。
一個再大的系統(tǒng),都可以劃分一個個小的模塊埠通,分而治之
頁面結(jié)構(gòu)化赎离,并不是新玩意,是當(dāng)時做web的一套代碼風(fēng)格端辱。下圖是當(dāng)時做Web總結(jié)組件化的一張圖×禾蓿現(xiàn)在看來虽画,也就并沒有過時
頁面被劃分問一個個區(qū)域的模塊,有自身的邏輯和規(guī)劃荣病。有人說码撰,這不就是一個個組件嘛。然后“頁面結(jié)構(gòu)化”并不是指組件个盆。
例如上圖的tabContainer脖岛,imgsContainer,listContainer颊亮,每一個模塊都有自己的渲染模板(xml)柴梆,請求的數(shù)據(jù)的CGI(數(shù)據(jù)源),自身的事件綁定(listener) 终惑,狀態(tài)機(jī)(生命周期)绍在,并不只是一個組件,而是一個個有自己生命力雹有,能自己管理的小頁面偿渡。
根據(jù)頁面結(jié)構(gòu),劃分出一個個獨(dú)立維護(hù)模塊件舵,這就是頁面結(jié)構(gòu)化卸察。
頁面結(jié)構(gòu)化(Lego)與組件化的區(qū)別
組件處于通用性,是不帶業(yè)務(wù)邏輯的铅祸。而頁面結(jié)構(gòu)化是帶業(yè)務(wù)邏輯坑质。
頁面結(jié)構(gòu)化目的是為了代碼維護(hù)性,項目管理临梗,優(yōu)化涡扼。組件復(fù)用可以有,但不是必要
組件與Lego不沖突盟庞。組件 +數(shù)據(jù)吃沪,業(yè)務(wù)邏輯 = Lego
下面就以問答的形式,用日跡評論贊項目實(shí)戰(zhàn)什猖,來講解Lego好處
四票彪,分析頁面結(jié)構(gòu)化特性
Lego自己拉取自己的數(shù)據(jù),如果一個頁面5,6個模塊不狮,就拉5,6分PB協(xié)議降铸,談何性能?
這里帶出Lego兩個特性:
每個Lego是有自己的數(shù)據(jù)摇零,并不是一定要自己拉取推掸,數(shù)據(jù)可以有其他Lego傳遞
Lego有父子關(guān)系。一個頁面/Activity需要一個頂層Lego管理
日跡首頁評論贊
public FeedCommentLikeLego(Context context, Activity activity, ViewGroup parentView, HomeFeedItem feedItem, int feedType) {
? ? super(context, parentView);
? ? mHomeFeedItem = feedItem;
? ? mFeedItem = feedItem.mFeedBasicItem;
? ? mActivity = activity;
? ? mFeedType = feedType;
? ? mLikeManager = (LikeManager) SuperManager.getAppManager(SuperManager.LIKE_MANAGER);
? ? mParentView = LayoutInflater.from(context).inflate(R.layout.qqstory_feed_commentlike_view, parentView, true);
? ? // 頁面結(jié)構(gòu)
? ? FeedCommentLego commentLego = new FeedCommentLego(mContext, mParentView, mFeedItem, mFeedType);
? ? FeedLikeLego likeLego = FeedLikeLego.createIndexFeedLikeLego(mContext, activity, mParentView, mFeedItem, mFeedType);
? ? addLego(LEGO_KEY_COMMENT, commentLego);
? ? addLego(LEGO_KEY_LIKE, likeLego);
? ? commentLego.feed(mHomeFeedItem.getCommentList());
? ? likeLego.feed(mHomeFeedItem.getLikeEntryList());
? ? boot();
}
從FeedCommentLikeLego的構(gòu)造方法,我們得知
我是爸爸谅畅,我有兩個兒子
我兩個兒子不爭氣登渣,需要我來喂養(yǎng)數(shù)據(jù),自己不會掙錢(自己不拉數(shù)據(jù))
全家我是一家之主毡泻,啟動我說了算(Lego啟動boot后胜茧,會自己拉數(shù)據(jù)自己渲染,同時子Lego也會相繼boot)
日跡710這里就有場景牙捉,體驗出Lego切換數(shù)據(jù)源的優(yōu)勢竹揍。
【首頁】出于性能優(yōu)化,都會做請求合并邪铲。返回多個Feed的視頻列表芬位,評論贊列表數(shù)據(jù)。
commentLego.feed(mHomeFeedItem.getCommentList());
likeLego.feed(mHomeFeedItem.getLikeEntryList());
被喂養(yǎng)數(shù)據(jù)后带到,Lego內(nèi)部的DataProvider將不啟動
【詳情頁】同一Lego昧碉,默認(rèn)情況就會啟動資金的DataProvider,會自己拉數(shù)據(jù)
@Override
public LegoDataProvider getDataProvider() {
? ? return new FeedLikeDataProvider(this, mIsDetailPage);
}
一個Lego類是究竟是什么揽惹?Lego類之間的紐帶被饿?
大部分頁面的渲染流程線,如下圖
我們把這些常用的網(wǎng)絡(luò)請求搪搏,處理數(shù)據(jù)狭握,事件綁定,上報疯溺,容錯處理等一系列邏輯方法论颅,以頁面塊為單位封裝成一個Lego模塊。
這樣的一個抽象層Lego囱嫩,我們可以清晰地看到該頁面塊恃疯,請求的數(shù)據(jù)是什么,綁定了什么事件墨闲,做了什么上報今妄,出錯怎么處理。
最后加上生命周期鸳碧,頁面結(jié)構(gòu)化的Lego盾鳞,已經(jīng)算是一個完整的功能單元了。
繼承LegoBase瞻离,有幾個核心的方法需要重寫:
還有生命周期方法可以重寫雁仲,但不是必要的。
你閱讀/接手一個Lego類琐脏,會是件很輕松的事情。一個Lego類,核心方法這幾個日裙,其余都是業(yè)務(wù)邏輯方法吹艇。
改事件去該Lego的EventHandler,數(shù)據(jù)要改去DataProvider昂拂,產(chǎn)品要求大V才展示底部尾巴受神,好,去render方法找格侯。
Lego之間的紐帶鼻听,有三個:
parentView(公用xml)
feedData(公用數(shù)據(jù))
getLego(Lego關(guān)系)
四,總結(jié)
Lego的核心思想是:頁面結(jié)構(gòu)分模塊联四,分而治之撑碴。解耦,代碼可讀性高朝墩,底層統(tǒng)一優(yōu)化醉拓。
在使用了兩個版本之后,感覺完成度還是不夠收苏。
頂層Lego情況復(fù)雜亿卤,底層統(tǒng)一優(yōu)化不好做
接口之間約束,不夠自由
但是對比MVP鹿霸,Lego能體驗出輕便排吴,邏輯清晰,方法數(shù)量少的優(yōu)勢懦鼠。
Lego頁面結(jié)構(gòu)化的應(yīng)用其實(shí)還在嘗試階段钻哩。以上算我的一些個人思考和總結(jié)。
更多MelonTeam團(tuán)隊文章葛闷,請點(diǎn)擊下方“閱讀原文”
如果您覺得我們的內(nèi)容還不錯憋槐,就請轉(zhuǎn)發(fā)到朋友圈,和小伙伴一起分享吧~
閱