[設(shè)計模式]記一次開源庫的重構(gòu)歷程

上周花了幾天重寫了我之前的IndexableStickyListView庫,重構(gòu)成RecyclerView版本:IndexableRecyclerView。

關(guān)鍵字:Wrapper(包裝)模式、Adapter(適配器)模式、Observer(觀察者)模式;

聯(lián)系人Demo

老版本的問題


1、使用者的實體類需要extends庫的IndexEntity。

Java是單繼承,多實現(xiàn);所以繼承只有一次機會,而把這個寶貴的機會讓給一個第三方庫是不合適的!

2、HeaderView的局限較大,只能添加和ListView的Adapter相同布局的HeaderView,或者普通View。

應(yīng)該可以添加任意布局并且可以和索引相關(guān)聯(lián)的HeaderView/FooterView。

3、沒有留給使用者足夠的UI定制自由度,比如綁定數(shù)據(jù)時的菊花,搜索時的菊花等提示信息,庫內(nèi)部提供死一個固定的樣式。

作為一個功能為驅(qū)動的第三方庫,UI的樣式應(yīng)該完全交給使用者自由定制。

4、ListView跟不上時代,需要RecyclerView。

RecyclerView更優(yōu)雅的設(shè)計、以及更強大的功能,遷移到RecyclerView上是應(yīng)該的。

新版本解決方式


1、Wrapper模式

在老版本的問題1中,庫占用了寶貴的繼承機會,這種設(shè)計是不合理的;作為一個第三方庫,除非必要,否則應(yīng)當(dāng)以implements去實現(xiàn)繼承關(guān)系。

而使implements替代extends,我這里使用了裝飾者模式(包裝模式)。

裝飾模式又名包裝(Wrapper)模式。裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關(guān)系的一個替代方案。

裝飾模式以對使用者透明的方式動態(tài)地給一個對象附加上更多的責(zé)任;裝飾模式可以在不創(chuàng)造更多子類的情況下,將對象的功能加以擴展。

具體實現(xiàn):

  • 原版本:
// 使用者繼承IndexEntity:
public class CityEntity extends IndexEntity {
   private String name;
?
   @Override
   public String getName() {
       return name;
   }
?
   @Override
   public void setName(String name) {
       this.name = name;
   }
}

庫的IndexEntity里包含一些拼音、首字母等屬性:

public abstract class IndexEntity {
   private String firstSpell;
   private String spell;
   ...
}
  • 新版本:
// 使用者實現(xiàn)IndexableEntity:
public class CityEntity implements IndexableEntity {
   private String name;
?
   @Override
   public String getFieldIndexBy() {
       return name; // return 你需要根據(jù)該屬性排序的field
   }
?
   @Override
   public void setFieldIndexBy(String indexByField) {
       this.name = indexByField; // 同上
   }
}

使用者傳遞給庫的數(shù)據(jù)源只需要實現(xiàn)該IndexableEntity即可,庫把它包裝成EntityWrapper,內(nèi)部數(shù)據(jù)的處理其實都是EntityWrapper。

class EntityWrapper<T> {
   private String index;
   private String pinyin;
   private T data;
  ...
}

標(biāo)準(zhǔn)的Wrapper模式同樣需要實現(xiàn)IndexableEntity,這里并沒有實現(xiàn)是為了兼容HeaderView/FooterView的數(shù)據(jù)源情況,所以可以認為是Wrapper模式的變種。

2、Adapter模式

在老版本的問題2中,HeaderView的局限較大,是因為老版本沒有提供從數(shù)據(jù)源視圖的映射。

使用Adapter,可以輕松實現(xiàn)這種映射關(guān)系。

適配器模式把一個類的接口變換成使用者所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。

Android中的ListView對應(yīng)的ListAdapter / RecyclerView對應(yīng)的Adapter就是典型的應(yīng)用場景。

具體實現(xiàn):

  • 原版本:
mIndexableListView.bindDatas(datas,IndexHeaderEntity headerEntity);
// 只提供數(shù)據(jù)源,而沒有提供視圖的定制:
IndexHeaderEntity<CityEntity> gpsHeader = new IndexHeaderEntity<>("定", "GPS自動定位", gpsIndexEntityList);
  • 新版本:
indexableLayout.addHeaderAdapter(IndexableHeaderAdapter adapter)
// Adapter:
public abstract class IndexableHeaderAdapter<T> {
   // 設(shè)置數(shù)據(jù)源
   public IndexableHeaderAdapter(String index, String indexTitle, List<T> datas) {
       ...
   }
   // ItemType,配合RecyclerView的Adapter
   public abstract int getItemViewType();
?   // 創(chuàng)建視圖
   public abstract RecyclerView.ViewHolder onCreateContentViewHolder(ViewGroup parent);
?   // 設(shè)置視圖數(shù)據(jù)
   public abstract void onBindContentViewHolder(RecyclerView.ViewHolder holder, T entity);
}

?通過HeaderAdapter,庫內(nèi)部經(jīng)過一些處理,可以使數(shù)據(jù)源映射到使用者期望的視圖。


自由定制的HeaderView

3、Observer模式

在Android的Adapter場景中,一般Adapter模式會搭配Obsever模式一起使用。因為Android中ListView/RecyclerView和Adapter是一個MVC的設(shè)計:

ListView/RecyclerView是View,Adapter是Controller,數(shù)據(jù)源是Model。

既然V與M是分離的,那么當(dāng)數(shù)據(jù)有更新時,V顯然無法自動更新,Adapter必須實時監(jiān)控數(shù)據(jù)變化并刷新V,這里就需要用到Observer(觀察者模式)。

觀察者模式定義了一種一對多的依賴關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個主題對象。這個主題對象在狀態(tài)上發(fā)生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。

具體實現(xiàn):

// 被觀察者:
public abstract class IndexableHeaderAdapter<T> {
   private final DataSetObservable mDataSetObservable = new DataSetObservable();
   ...
?
   public void notifyDataSetChanged() {
       mDataSetObservable.notifyChanged();
   }
?
   void registerDataSetObserver(DataSetObserver observer) {
       mDataSetObservable.registerObserver(observer);
   }
?
   void unregisterDataSetObserver(DataSetObserver observer) {
       mDataSetObservable.unregisterObserver(observer);
   }
}
// 注冊觀察者:
public class IndexableLayout extends FrameLayout {
   private DataSetObserver mHeaderDataSetObserver = new DataSetObserver() {

       @Override
       public void onChanged() {
           if (mRealAdapter != null) {
               mRealAdapter.notifyDataSetChanged();
           }
       }
   };

   public <T> void addHeaderAdapter(IndexableHeaderAdapter<T> adapter) {
       adapter.registerDataSetObserver(mHeaderDataSetObserver);
       ...
   }
}

一旦數(shù)據(jù)源發(fā)生變化:
1、調(diào)用Adapter的notifyDataSetChanged();
2、通知所有觀察者數(shù)據(jù)發(fā)生變化:observable.notifyChanged();
3、回調(diào)所有觀察者的onChanged()

這樣就完成整個觀察過程,上面使用的DataSetOberver,DataSetObservable類是借用了現(xiàn)有的Android內(nèi)的類,當(dāng)這些通知的類型不夠時,可根據(jù)這兩個類進行拓展。

觀察者模式在處理一對多的依賴關(guān)系的同時,做到了優(yōu)雅的解耦。

另外:

在老版本問題3中,為了給留使用者足夠的UI定制自由度,也需要使用Observer模式,比如初始化數(shù)據(jù)時,庫提供一個初始化結(jié)束時的回調(diào),以便使用者自由操作UI。

mProgressBar.setVisibility(View.VISIBLE);
adapter.setDatas(mDatas, new IndexableAdapter.IndexCallback<CityEntity>() {
    @Override
    public void onFinished(List<CityEntity> datas) {
        // 數(shù)據(jù)處理完成后回調(diào)
        mProgressBar.setVisibility(View.GONE);
    }
});

總結(jié)

一個庫的完成不是終點,而是一個起點。

在我們開發(fā)的過程中,可能會推翻之前的一些想法,這時庫的發(fā)展方向(包括代碼質(zhì)量)可能就跑偏了,或者在早些開發(fā)時的設(shè)計就并不完美。

所以在庫完成后,一次重構(gòu)就很有必要了;這次重構(gòu)會讓你考慮的更全面,一些設(shè)計模式的運用也就呼之欲出。

小伙伴們,重構(gòu)起來吧!在精益求精中進步~

最后,貼上IndexableRecyclerView的項目地址: GitHub

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,057評論 25 709
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,568評論 19 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,706評論 18 399
  • 你好 失敗者
    絕望中的失敗者閱讀 248評論 0 0
  • 由老修女引領(lǐng),新來的修女跟著她來到剛才辦公室另一角的“病房”。門口偏右有張小床,上面躺著一個小男孩。一個銀發(fā)黑衣的...
    夜影散盡滄桑閱讀 527評論 2 1

友情鏈接更多精彩內(nèi)容