本系列文章集合:從 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ù)接口
- IBaseView - ui 接口的 updata 方法就是更新數(shù)據(jù)顯示方法
#2 處理 P 層持有V 層對(duì)象可能造成的內(nèi)存泄露
這里我們首先規(guī)范 P 層與 V 層的綁定和解綁,然后同步 V 層的生命周期
#3 抽象 P 層的公共代碼
在上面我們給 P 層添加了和 V 層綁定奸披,解綁的方法昏名,這個(gè)方法每個(gè) P 層的類都會(huì)用到的, 拿著這2個(gè)方法就是重復(fù)代碼阵面,公共方法轻局,是我們需要優(yōu)化的洪鸭,所以這里我們添加 P 層的抽象基類 (BasePersenter),封裝公共方法
#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)代碼了,泛型使用還是有些需要說的
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 接口
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ì)
類多了我就真心不會(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ù)赂蕴,就是好代碼。