從 MVP學(xué)習(xí)代碼封裝 (2) - 搭建 MVP 最基礎(chǔ)框架

本系列文章集合:從 MVP學(xué)習(xí)代碼封裝 (1) - 綜述

MVP 我就不說了,前面的章節(jié)說過了,我也相信能看到這里的萌新們對(duì)于 MVP 應(yīng)該都熟記于心了荠商,無需多言寂恬。作為我們代碼封裝的開始,我們先來熟悉代碼封裝的簡(jiǎn)單套路莱没,這里涉及到的是最簡(jiǎn)單的封裝套路初肉,是基礎(chǔ),不怎么涉及到設(shè)計(jì)模式饰躲。

這個(gè)模式就是:base 接口 —> abs 基類 —> 實(shí)現(xiàn)類

最基本的代碼封裝套路都是這個(gè)樣子:

  • base 接口的意思在于定于基礎(chǔ)功能方法牙咏,使用設(shè)計(jì)模式中 - 依賴倒置原則,作為對(duì)外暴露的統(tǒng)一類型使用嘹裂,常見使用的范圍就是泛型傳遞了妄壶。
  • abs 基類的意思在于封裝公共方法和參數(shù),主要是從代碼復(fù)用教導(dǎo)出發(fā)
  • 實(shí)現(xiàn)類寄狼,不用說了盯拱,具體的類,繼承 abs 基類

下面我會(huì)一部一部的記錄代碼封裝的過程和思考例嘱,盡量詳細(xì)狡逢,力求給萌新們提供有用的幫助

項(xiàng)目地址: BW-MVPDemo

需要說一下,每個(gè)modle 都是可以獨(dú)立運(yùn)行的拼卵,step1 的 modle 都是本節(jié)文章的奢浑,沒有 step1_4,我寫的時(shí)候把 #3 和#4寫一起了腋腮,大家注意下雀彼。


#1 我們簡(jiǎn)單建立一個(gè) MVP 的結(jié)構(gòu)

因?yàn)楹芎?jiǎn)單,原始即寡,大家都會(huì)徊哑,我就不上代碼了,直接 UML 走起了聪富,自己畫的莺丑,可能不是很好看,大家見諒啊墩蔓。數(shù)據(jù)層先簡(jiǎn)單的用一個(gè)字符串代替梢莽,后面會(huì)跑真實(shí)數(shù)據(jù)接口


Snip20171125_36.png
  • IBaseView - ui 接口的 updata 方法就是更新數(shù)據(jù)顯示方法

#2 處理 P 層持有V 層對(duì)象可能造成的內(nèi)存泄露

這里我們首先規(guī)范 P 層與 V 層的綁定和解綁,然后同步 V 層的生命周期

Snip20171125_37.png

#3 抽象 P 層的公共代碼

在上面我們給 P 層添加了和 V 層綁定奸披,解綁的方法昏名,這個(gè)方法每個(gè) P 層的類都會(huì)用到的, 拿著這2個(gè)方法就是重復(fù)代碼阵面,公共方法轻局,是我們需要優(yōu)化的洪鸭,所以這里我們添加 P 層的抽象基類 (BasePersenter),封裝公共方法

Snip20171125_38.png

#4 P 層引入泛型仑扑,支持動(dòng)態(tài) V 層對(duì)象類型

不要忘了我們封裝代碼的初衷览爵,更簡(jiǎn)單,更好用夫壁,更好看拾枣,更好改沃疮。我們雖然抽象了 P 層的 abs 基類盒让,但是持有的 V 層對(duì)象類型是 V 層的 根base 接口類型,在具體的 P 對(duì)象中還是要把 V 層對(duì)象類型轉(zhuǎn)換成我們需要的具體的 V 層對(duì)象類型司蔬,這一步V 層對(duì)象的類型轉(zhuǎn)換邑茄,其實(shí)本質(zhì)上也是重復(fù)代碼,只不過是代碼量很少俊啼,但是我要說我們一定要根本一顆盡善盡美的心肺缕,才能越做越好。這是題外話了授帕,切回正題同木,頻繁的類型轉(zhuǎn)換會(huì)讓我們?cè)诰幋a時(shí)不是很靈活,而且類型轉(zhuǎn)換寫多了可能你讓我們有點(diǎn)暈

所以本著盡自己最大努力的想法跛十,這里引入動(dòng)態(tài)類型傳遞 - 泛型彤路。這里我就要加上點(diǎn)代碼了,泛型使用還是有些需要說的

Snip20171126_40.png

abs P層基類

public abstract class BasePersenter<V extends IBaseView> {

    protected V mIBaseView;

    public void attachView(V baseViewa) {
        this.mIBaseView = baseViewa;
    }

    public V getView() {
        return mIBaseView;
    }

    public void detachView() {
        this.mIBaseView = null;
    }
}

P層具體實(shí)現(xiàn)類

public class NewsPersenter extends BasePersenter<IBaseView> {
    .......
}

放上代碼是為了看泛型使用的芥映,具體的代碼沒什么洲尊,想看的去看 demo


#5 抽象 V 層的公共代碼 , V 層引入泛型奈偏,支持動(dòng)態(tài) V坞嘀,P 層對(duì)象類型

P 層我們抽象了 abs 基類,那么同樣 V 層我們也是需要抽象 V 層的 abs 基類惊来,activiyt 的公共方法和參數(shù)有很多的丽涩。

另外在我們抽象的 V 層的 abs 基類中,P 層對(duì)象的類型也是不確定的裁蚁,這里我們同樣要在 V 層中使用 泛型 動(dòng)態(tài)類型内狸,這里需要注意,我們?cè)?P 層abs 基類已經(jīng)使用了 V 層對(duì)象的泛型厘擂,所以在寫 V 層中 P 層對(duì)象的泛型類型時(shí)要注意類型傳遞昆淡,因?yàn)?V 層的接口根據(jù)實(shí)際業(yè)務(wù)可能會(huì)有很多層,這里我就多加了一層 view 接口

Snip20171126_41.png

V 層的abs 基類

public abstract class BaseActivity<V extends IBaseView, P extends BasePersenter<V>> extends AppCompatActivity implements IBaseView {

    public P mBasePersenter;

    protected abstract P createPersenter();

    public P getPersenter() {
        if (mBasePersenter == null) {
            mBasePersenter = createPersenter();
        }
        return mBasePersenter;
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBasePersenter = createPersenter();
        mBasePersenter.attachView((V) this);
    }

    @Override
    protected void onDestroy() {
        if (mBasePersenter != null) {
            mBasePersenter.detachView();
        }
        super.onDestroy();
    }
}

V 層的具體實(shí)現(xiàn)類

public class NewsActivity extends BaseActivity<INewsView, NewsPersenter> implements INewsView {

    private TextView tx_content;
    private Button btn_getNews;

    @Override
    protected NewsPersenter createPersenter() {
        return new NewsPersenter();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tx_content = (TextView) findViewById(R.id.content);
        btn_getNews = (Button) findViewById(R.id.title);

        btn_getNews.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (getPersenter() != null) {
                    getPersenter().update();
                }
            }
        });
    }

    @Override
    public void update(String data) {
        tx_content.setText(data);
    }

    @Override
    public void showNewsList() {
    }
}

在 BaseActivity 中刽严,使用了2個(gè)泛型昂灵,一個(gè)是 V層 的具體子類的類型避凝,一個(gè)是 P 層的類型,因?yàn)?P 層的 bas 基類需要填入 V 層對(duì)象的實(shí)現(xiàn)類型眨补,所以在 BaseActivity 這個(gè) V 層的 abs 基類中需要知道具體的實(shí)現(xiàn)子類的類型管削,以方便傳遞給 P 層,所以泛型寫成這樣,萌新們注意啊撑螺,我剛接觸時(shí)這里也是理解了一段時(shí)間的

public abstract class BaseActivity<V extends IBaseView, P extends BasePersenter<V>> extends AppCompatActivity implements IBaseView 

另外注意這里:

   mBasePersenter = createPersenter();
   mBasePersenter.attachView((V) this);

在 P 對(duì)象綁定 V 對(duì)象時(shí)含思,其實(shí)這里因?yàn)榉謩e對(duì) P,V 使用了泛型的緣故,這里實(shí)際上都是實(shí)際的實(shí)現(xiàn)類了甘晤,需要強(qiáng)轉(zhuǎn) V 的類型為實(shí)現(xiàn)類類型

這一點(diǎn)還有需要注意接口實(shí)現(xiàn)的傳遞性含潘,abs 基類實(shí)現(xiàn) V 層的根業(yè)務(wù)接口,V 層實(shí)現(xiàn)類需要實(shí)現(xiàn)具體業(yè)務(wù)接口线婚,具體業(yè)務(wù)接口是繼承自V 層的根業(yè)務(wù)接口的遏弱,哈哈,說的有點(diǎn)像繞口令了塞弊,我也這么覺得漱逸,反正這些都是涉及的類型的問題,因?yàn)橛昧朔盒陀窝兀赃@種具體類型和跟類型之間的轉(zhuǎn)換肯定是少不了的饰抒,大家多多站在這里注意啊,這里很容易出問題的诀黍,類多了就很繞了袋坑。

INewsView -> IBaseView 

BaseActivity -> implements IBaseView 
NewsActivity-> implements INewsView {

#6 使用動(dòng)態(tài)代理處理 v 對(duì)象的非空判斷

不知道大家用沒用過動(dòng)態(tài)代理啊,動(dòng)態(tài)代理是可以代理目標(biāo)對(duì)象中的任何方法蔗草,感覺和 hook 很像咒彤。這里我們目前只能對(duì) P層中的 V層對(duì)象做動(dòng)態(tài)代理,而不能對(duì)V 層中 P 層對(duì)象做代理咒精,因?yàn)閯?dòng)態(tài)代理需要目標(biāo)對(duì)象實(shí)現(xiàn)一個(gè)接口镶柱,基類不行。


public abstract class BasePersenter<V extends IBaseView> {

    protected V mIBaseView;

    protected void getProxyView() {
        mIBaseView = (V) Proxy.newProxyInstance(mIBaseView.getClass().getClassLoader(), mIBaseView.getClass().getInterfaces(), new NotNullnvocationHandler(mIBaseView));
    }

    public void attachView(V baseView) {
        this.mIBaseView = baseView;
        getProxyView();
    }

    public V getView() {
        return mIBaseView;
    }

    public void detachView() {
        this.mIBaseView = null;
    }

    public class NotNullnvocationHandler implements InvocationHandler {

        protected V v;

        public NotNullnvocationHandler(V v){
            this.v = v;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            if (mIBaseView == null) {
                return null;
            }
            return method.invoke(v, args);
        }
    }
}

這樣我們?cè)?getView() 時(shí)就不用進(jìn)行非空判斷了模叙。


#7 添加對(duì) fragment / 自定義 viewGroup 的支持

上面我們提供了 activity 對(duì)應(yīng)mvp 實(shí)現(xiàn)歇拆,但是我們還有 fragment / 自定義 viewGroup 啊,這2個(gè)我們也是經(jīng)常用的啊范咨,在 mvp 架構(gòu)方面看其實(shí) fragment / 自定義 viewGroup 和 avtiviyt 沒什么不同的故觅,區(qū)別的是不同的 UI 組件罷了,他們?cè)?mvp 中處于的位置相同渠啊,包含的屬性和功能應(yīng)該也是相同的才對(duì)

Snip20171203_47.png

類多了我就真心不會(huì)畫 UML 類圖了

BaseActivity:

public abstract class BaseActivity<V extends IBaseView, P extends BasePersenter<V>> extends AppCompatActivity implements IBaseView {

    private P mBasePersenter;

    protected abstract P createPersenter();

    public P getPersenter() {
        return mBasePersenter;
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBasePersenter = createPersenter();
        mBasePersenter.attachView((V) this);
    }

    @Override
    protected void onDestroy() {
        if (mBasePersenter != null) {
            mBasePersenter.detachView();
        }
        super.onDestroy();
    }

}

BaseFragment

public abstract class BaseFragment<V extends IBaseView, P extends BasePersenter<V>> extends Fragment implements IBaseView {

    private P mBasePersenter;

    protected abstract P createPersenter();

    public P getPersenter() {
        return mBasePersenter;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBasePersenter = createPersenter();
        mBasePersenter.attachView((V) this);
    }

    @Override
    public void onDestroy() {
        if (mBasePersenter != null) {
            mBasePersenter.detachView();
        }
        super.onDestroy();
    }

}

BaseCustomeView:

public abstract class BaseCustomeView<V extends IBaseView, P extends BasePersenter<V>> extends AppCompatButton implements IBaseView {

    private P mBasePersenter;
    private onCustomeClickListener customeClickListener;

    protected abstract P createPersenter();

    public P getPersenter() {
        return mBasePersenter;
    }

    @Override
    public void update(String data) {

    }

    public BaseCustomeView(Context context) {
        super(context);
    }

    public BaseCustomeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public BaseCustomeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setCustomeClickListener(onCustomeClickListener customeClickListener) {
        this.customeClickListener = customeClickListener;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mBasePersenter = createPersenter();
        mBasePersenter.attachView((V) this);
    }

    @Override
    public void setOnClickListener(@Nullable final OnClickListener l) {

        super.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (customeClickListener != null) {
                    customeClickListener.onCustomeClick(v);
                }
                l.onClick(v);
            }
        });
    }

    @Override
    protected void onDetachedFromWindow() {
        if (mBasePersenter != null) {
            mBasePersenter.detachView();
        }
        super.onDetachedFromWindow();
    }

    public interface onCustomeClickListener {

        void onCustomeClick(View view);

    }

}

說真心話输吏,寫 BaseFragment / BaseCustomeView 我都是直接復(fù)制粘貼的BaseActivity 的代碼,說到這里各位看官應(yīng)該也知道了替蛉,我們下一步該干什么了吧贯溅。


我最想說的

  • 代碼抽象問題:
    看到這里拄氯,大家對(duì)于上面我說的那個(gè)問題 “ 說到這里各位看官應(yīng)該也知道了,我們下一步該干什么了吧 ” 它浅,大家怎么看译柏,一般認(rèn)為我們對(duì)于 BaseActivity / BaseFragment / BaseCustomeView 中重復(fù)的代碼應(yīng)該再抽象一下,再抽象出一個(gè)頂層 abs 基類出來姐霍,我一開始第一反應(yīng)就是這樣鄙麦,也去這樣寫了,但是呢我寫不出來镊折,為啥胯府?大家想沒想過,aseActivity / BaseFragment / BaseCustomeView 都是我們對(duì)應(yīng)不同類型的系統(tǒng)UI 組件類型才分離出來的腌乡,天然就要去繼承 Activity / Fragment / View 這3個(gè)系統(tǒng)類盟劫,然后基于 java 繼承的單一性夜牡,我們?cè)趺丛偃ダ^承一個(gè)類呢与纽,我們寫的已經(jīng)是抽象類了,是要給我們自己的 Activity / Fragment / View 去繼承的塘装。也許有人要說急迂,我們可以使用 裝飾著設(shè)計(jì)模式啊,使用一個(gè) wrapper 容器類來添加功能啊蹦肴,但是我要說不合適這里僚碎,我們要是使用了容器類,那么我們的 abs 基類就不再是 Activity / Fragment / View阴幌。所以我想說呢勺阐,大部分重復(fù)代碼是可以通過封裝來省略的,但是有的不行矛双,不要因?yàn)榉庋b去封裝渊抽,封裝的根本目的還是優(yōu)化我們現(xiàn)有的代碼結(jié)構(gòu),一切以實(shí)際需求為準(zhǔn)议忽,簡(jiǎn)單好用懒闷,不復(fù)雜才是真的好。

  • 接口繼承和泛型類型限定問題:
    不知道大家對(duì) IBaseView這個(gè) view 層的根接口有沒有想過栈幸,為啥我們?cè)趯?INewsView 這個(gè)具體的 V 層業(yè)務(wù)接口的時(shí)候愤估,我們要去繼承 IBaseView 呢。因?yàn)槲覀兿薅?BaseActivity<V extends IBaseView, P extends BasePersenter<V>> 的泛型 V 的取值范圍速址,在我們寫一個(gè)具體的 activity 時(shí)玩焰,NewsActivity extends BaseActivity<INewsView, NewsPersenter> 我們想要在 NewsPersenter 中操作的即是 INewsView 這個(gè)業(yè)務(wù)接口,也是 IBaseView 這個(gè) V 層視圖對(duì)象芍锚,所以我們讓V 的業(yè)務(wù)接口繼承跟接口: INewsView extends IBaseView 昔园。當(dāng)然還有另一種寫法荔棉,對(duì)于 BaseActivity,BasePersenter 中的 V 泛型不做任何限制蒿赢,這樣寫也行润樱,但是失去了泛型可以實(shí)現(xiàn)的類型驗(yàn)證功能,我不太喜歡羡棵,這點(diǎn)還是仁者見仁壹若,智者見智吧,沒有一定正確的皂冰,只有大家的個(gè)人喜好和具體需求的適應(yīng)性了店展,能適應(yīng)好具體需求,達(dá)到易擴(kuò)展秃流,好維護(hù)赂蕴,就是好代碼。


參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舶胀,一起剝皮案震驚了整個(gè)濱河市概说,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嚣伐,老刑警劉巖糖赔,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異轩端,居然都是意外死亡放典,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門基茵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奋构,“玉大人,你說我怎么就攤上這事拱层∶志剩” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵舱呻,是天一觀的道長(zhǎng)醋火。 經(jīng)常有香客問我,道長(zhǎng)箱吕,這世上最難降的妖魔是什么芥驳? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮茬高,結(jié)果婚禮上兆旬,老公的妹妹穿的比我還像新娘。我一直安慰自己怎栽,他們只是感情好丽猬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布宿饱。 她就那樣靜靜地躺著,像睡著了一般脚祟。 火紅的嫁衣襯著肌膚如雪谬以。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天由桌,我揣著相機(jī)與錄音为黎,去河邊找鬼。 笑死行您,一個(gè)胖子當(dāng)著我的面吹牛铭乾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播娃循,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼炕檩,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了捌斧?” 一聲冷哼從身側(cè)響起笛质,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎骤星,沒想到半個(gè)月后经瓷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爆哑,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洞难,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了揭朝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片队贱。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖潭袱,靈堂內(nèi)的尸體忽然破棺而出柱嫌,到底是詐尸還是另有隱情,我是刑警寧澤屯换,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布编丘,位于F島的核電站,受9級(jí)特大地震影響彤悔,放射性物質(zhì)發(fā)生泄漏嘉抓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一晕窑、第九天 我趴在偏房一處隱蔽的房頂上張望抑片。 院中可真熱鬧,春花似錦杨赤、人聲如沸敞斋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽植捎。三九已至衙解,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間焰枢,已是汗流浹背丢郊。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留医咨,地道東北人枫匾。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像拟淮,于是被迫代替她去往敵國(guó)和親干茉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容