Jianwoo中的設(shè)計(jì)模式(2) — 觀察者模式

什么是觀察者模式?

網(wǎng)上有很多種對于觀察者模式的描述,我簡單的說個(gè)比喻大概就能理解:我們常用的QQ郵箱识脆,有訂閱功能桩撮,你訂閱了一個(gè)郵箱墨坚,以后這個(gè)郵箱每次有什么新內(nèi)容获讳,都會主動通知到你的郵箱里艰毒,你并不需要主動去訂閱賬號去看內(nèi)容掉盅。當(dāng)然我們進(jìn)入郵箱獲取郵箱是一個(gè)主動請求接口的過程朝扼,而觀察者模式是一個(gè)不需要主動就能獲取通知的一種“訂閱”關(guān)系赃阀,我訂閱了你,以后你只要發(fā)送一個(gè)消息擎颖,我都能收到榛斯,有3個(gè)訂閱者那就3個(gè)都能收到,所以觀察者模式一般會有“注冊訂閱”和“取消訂閱”的兩個(gè)行為
我們在開發(fā)中有什么場景會運(yùn)用到觀察者模式呢肠仪,最常見的就是EventBus了肖抱,不過EventBus是通過注解來獲取訂閱者的回調(diào)方法,而我們在開發(fā)中一般是運(yùn)用接口來獲取回調(diào)异旧。我們在Android中所用到的各種監(jiān)聽setOnclickListener意述,setOnLongClicklistener等也是觀察者模式,只不過這里是一對一的觀察者模式吮蛹,多對一有沒有呢荤崇,Android里那些addXXXListener就屬于多對一的觀察者模式了,追進(jìn)源碼你會發(fā)現(xiàn)內(nèi)部是通過一個(gè)集合來維護(hù)這些“訂閱者”潮针,說了這么多我們來聊一聊簡物中運(yùn)用觀察者模式的場景

簡物中的觀察者模式

簡物中除了使用EventBus作為發(fā)布/訂閱事件總線术荤,也使用了自己寫的觀察者莫斯,簡物是一個(gè)電商APP每篷,自然有商品詳情界面瓣戚,而簡物中的商品詳情又有入口可以進(jìn)入到別的商品,也就是可以無限打開多個(gè)商品詳情焦读,商品詳情下方的購物車區(qū)域有一個(gè)商品數(shù)量顯示子库,點(diǎn)擊購物車可以進(jìn)入購物車界面,旁邊的添加購物車可以添加當(dāng)前商品到購物車
那需求是什么呢矗晃?我可以打開多個(gè)詳情界面仑嗅,也可以進(jìn)入購物車界面,我在任意一個(gè)界面執(zhí)行了添加商品到購物車或者在購物車列表進(jìn)行了加減商品數(shù)量或者刪除了商品张症,都要立即更新商品數(shù)量到每個(gè)商品界面仓技,如果我們不用觀察者模式那我們能怎么實(shí)現(xiàn)呢,大概就是在返回Activity的時(shí)候在onResume生命周期方法的地方去主動調(diào)用獲取購物車數(shù)量更新到界面俗他,或者在當(dāng)前界面執(zhí)行了添加購物車的方法后同時(shí)也調(diào)用更新購物車數(shù)量的方法脖捻,這樣做確實(shí)能實(shí)現(xiàn)功能,但是這樣做并不優(yōu)雅而且要銜接一堆代碼兆衅,對于更新迭代也非常不便
對于程序中的功能地沮,除非是涉及到算法或者硬件或者底層的東西颜价,要實(shí)現(xiàn)一個(gè)常規(guī)應(yīng)用功能并不是難與不難的問題,可能初級新手也能實(shí)現(xiàn)這個(gè)功能诉濒,運(yùn)行的界面也是一模一樣,但是對于我們而言夕春,我們應(yīng)該要想著如何讓程序有更好的擴(kuò)展性未荒,讓我們的代碼有更深層次的結(jié)構(gòu)意義,這樣做不是為了突出與別人的不同及志,而是為了讓程序更健壯
這種場景下的功能片排,如果把要更新購物車數(shù)量的地方作為觀察者,把管理購物車增刪改查結(jié)果的地方作為被觀察者速侈,那我們只要給他們綁定一個(gè)訂閱與被訂閱的關(guān)系率寡,并且在我們添加、刪除倚搬、修改購物車的接口均給被訂閱者發(fā)布一個(gè)更新消息冶共,那這樣不是所有的訂閱者都能收到購物車被更改的消息,然后拿到訂閱者傳給我們的數(shù)據(jù)進(jìn)行更新
那我們怎么做呢每界,這里我直接使用java的Observer(訂閱者)和Observable(發(fā)布者)來實(shí)現(xiàn)觀察者模式捅僵,這是java直接封裝好的通用的觀察者模式模型,那我們怎么用呢眨层,首先在UI邏輯類(要更新購物車數(shù)量的地方)實(shí)現(xiàn)Observer接口庙楚,讓它成為一個(gè)觀察者,并且實(shí)現(xiàn)未實(shí)現(xiàn)的方法

public class SaleDetailCategory extends Category implements Observer{

    /**
     * 購物車商品數(shù)量
     */
    @Bind(R.id.sale_number)
    TextView mSaleNumber;

    public SaleDetailCategory(BaseActivity activity) {
        super(activity);
    }

    ...

    @Override
    public void update(Observable observable, Object data) {
         
    }
}

然后我們來封裝一個(gè)發(fā)布者類趴樱,那首先我大概說一下簡物中是如何獲取購物車數(shù)量馒闷,因?yàn)槊總€(gè)購物車實(shí)體都有購物車uuid(cart_uuid, String)以及購物車商品數(shù)量(goods_number, Integer),那計(jì)算購物車中商品數(shù)量的方法應(yīng)該是將所有cart_uuid的goods_number加起來叁征,因?yàn)橐粩嗟男薷膶?yīng)購物車的數(shù)量纳账,所以我用HashMap<String, Integer>來做,既然是更新購物車數(shù)量的包裝類航揉,那自然會有更新購物車列表塞祈、更新指定購物車數(shù)量、添加購物車商品帅涂、刪除購物車商品议薪、清空購物車商品(添加商品到訂單)的操作方法,那我就直接將這個(gè)類貼出來媳友,不做詳細(xì)描述啦

/**
 * Created by Barry on 2017/2/11.
 */
public class ShoppingCartState extends Observable{

    /**
     * 用于返回到購物車界面是否要自動刷新的一個(gè)標(biāo)識
     */
    private boolean mShoppingCartStateChanged;

    private HashMap<String, Integer> mShoppingCart = new HashMap<>();

    /**
     * 獲取購物車列表成功斯议,購物車列表刷新成功
     * @param carts
     */
    public void updateShoppingCart(List<CartBean.Cart> carts){
        mShoppingCart.clear();
        for(CartBean.Cart cart:carts){
            mShoppingCart.put(cart.getCart_uuid(), cart.getGoods_number());
        }
        notifyDataChanged();
    }

    /**
     * 獲取購物車總數(shù)量
     * @return
     */
    public int getShoppingCartNumber(){
        int shoppingCartNumber = 0;
        Iterator iterator = mShoppingCart.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry entry = (Map.Entry) iterator.next();
            Object value = entry.getValue();
            shoppingCartNumber += ((Integer)value).intValue();
        }
        return shoppingCartNumber;
    }

    /**
     * 更新指定商品購物車數(shù)量
     * @param cart_uuid
     * @param number
     */
    public void updateCartNumber(String cart_uuid, int number){
        if(mShoppingCart.containsKey(cart_uuid)){
            mShoppingCart.put(cart_uuid, number);
        }
        notifyDataChanged();
    }

    /**
     * 獲取購物車數(shù)量文案
     * @return
     */
    public String getShoppingCartNumberString(){
        return getShoppingCartNumber() + BaseUtils.getString(R.string.activity_saledetail_cart_has_sale_end);
    }

    /**
     * 商品詳情添加商品到購物車成功
     * @param cart
     */
    public void addShoppingCartSuccess(CartBean.Cart cart){
        if(BaseUtils.isEmpty(cart)){
            return;
        }
        mShoppingCart.put(cart.getCart_uuid(), cart.getGoods_number());
        notifyDataChanged();
    }

    /**
     * 提交購物車商品到訂單成功
     */
    public void addCartToOrderSuccess(){
        clearShoppingCart();
        notifyDataChanged();
    }

    public void deleteShoppingCartSuccess(String cart_uuid){
        if(mShoppingCart.containsKey(cart_uuid)){
            mShoppingCart.remove(cart_uuid);
        }
        notifyDataChanged();
    }

    /**
     * 清空購物車數(shù)量:提交訂單到購物車|退出登錄
     */
    public void clearShoppingCart(){
        mShoppingCart.clear();
        notifyDataChanged();
    }

    /**
     * 更新到觀察者
     */
    public void notifyDataChanged(){
        setChanged();
        notifyObservers();
    }

    /**
     * 購物車狀態(tài)改變
     */
    public void shoppingCartLoaded(){
        setShoppingCartStateChanged(false);
    }

    public boolean isShoppingCartStateChanged() {
        return mShoppingCartStateChanged;
    }

    public void setShoppingCartStateChanged(boolean shoppingCartStateChanged) {
        this.mShoppingCartStateChanged = shoppingCartStateChanged;
    }

}

那我們現(xiàn)在只要在對應(yīng)更新購物車接口回調(diào)的地方調(diào)用這個(gè)類的方法即可,我這里貼一個(gè)栗子吧

/**
 * Created by Barry on 2017/1/20.
 * 添加到購物車實(shí)現(xiàn)
 */
public class AddCartModelImpl extends BaseModelImpl implements AddCartModel {

    public AddCartModelImpl(BaseView baseView) {
        super(baseView);
    }

    @Override
    public AddCartView getListener() {
        return (AddCartView)baseView;
    }

    @Override
    public void addCart(CartParams cart) {
        addCart(cart, false);
    }

    @Override
    public void addCart(CartParams cart, boolean finish) {
        addCart(cart.getUser_uuid(), cart.getGoods_id(), cart.getGoods_img(), cart.getGoods_sn(), cart.getProduct_id(), cart.getGoods_name(), cart.getMarket_price(), cart.getGoods_price(), cart.getGoods_number(), cart.getGoods_attr(), cart.getGoods_attr_id(), finish);
    }

    @Override
    public void addCart(String user_uuid, int goods_id, String goods_img, String goods_sn, int product_id, String goods_name, String market_price, String goods_price, int goods_number, String goods_attr, String goods_attr_id, final boolean finish) {
        QHApi.addCart(user_uuid, goods_id, goods_img, goods_sn, product_id, goods_name, market_price, goods_price, goods_number, goods_attr, goods_attr_id, this, CartBean.class, new OkHttpClientManager.Callback<CartBean>() {
            @Override
            public void onFailure() {
                getListener().addCartError(NETWORK_ERROR);
            }

            @Override
            public void onResponse(CartBean o) {
                if(isEmpty(o)){
                    getListener().addCartError(ERROR);
                    return;
                }

                if(o.getStatus() != HttpCode.OK){
                    getListener().addCartError(o.getMessage());
                    return;
                }
                App.getInstance().shoppingCartStateChanged();
                getListener().addCartSuccess(finish);
                try{
                    /**
                     * 更新購物車數(shù)量
                     */
                    App.getInstance().getShoppingCartState().addShoppingCartSuccess(o.getItems().get(0));
                }catch (Exception e){
                    App.getInstance().getShoppingCartState().addShoppingCartSuccess(null);
                }
            }
        });
    }
}

那回調(diào)成功并且調(diào)用購物車數(shù)量更新包裝類之后醇锚,我們的訂閱者也能拿到消息哼御,并且更新界面啦坯临,怎么實(shí)現(xiàn)的呢

public class SaleDetailCategory extends Category implements Observer{

    /**
     * 購物車商品數(shù)量
     */
    @Bind(R.id.sale_number)
    TextView mSaleNumber;

    public SaleDetailCategory(BaseActivity activity) {
        super(activity);
    }

    ...

    @Override
    public void update(Observable observable, Object data) {
        if(observable instanceof ShoppingCartState){
            updateShoppingCartNumber();
        }
    }

    public void updateShoppingCartNumber(){
        mSaleNumber.setText(App.getInstance().getShoppingCartState().getShoppingCartNumberString());
        if(mCartWillDestoryInTimeLayout.getVisibility() == View.VISIBLE && App.getInstance().getShoppingCartState().getShoppingCartNumber() <= 0){
            startCartWillDestoryLeaveLayoutAnimation();
        }

        if(mCartWillDestoryInTimeLayout.getVisibility() == View.GONE && App.getInstance().getShoppingCartState().getShoppingCartNumber() > 0){
            shoppingCartAddAnimation();
        }
    }
}

不過還要記得在生命周期方法中注冊和取消注冊觀察者哦

public class SaleDetailActivity extends BaseActivity{

    @Override
    protected void setContentView() {
        setContentView(R.layout.activity_saledetail);
    }

    @Override
    protected void initCategory() {
        category = new SaleDetailCategory(this);
    }

     @Override
    protected void doOnResume() {
        if(firstRunning){
            App.getInstance().getShoppingCartState().addObserver(category);
        }
    }

    @Override
    protected void doOnDestroy() {
        super.doOnDestroy();
        if(!BaseUtils.isEmpty(category)){
            App.getInstance().getShoppingCartState().deleteObserver(category);
        }
    }

最后貼一個(gè)實(shí)現(xiàn)的效果圖,上傳后發(fā)現(xiàn)恋昼,不但變的不流暢而且界面中1px的分割線都消失了…
注意觀察GIF圖中底部購物車數(shù)量欄目數(shù)量的變化

更新購物車數(shù)量

以上看靠,完

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市液肌,隨后出現(xiàn)的幾起案子挟炬,更是在濱河造成了極大的恐慌,老刑警劉巖嗦哆,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谤祖,死亡現(xiàn)場離奇詭異,居然都是意外死亡老速,警方通過查閱死者的電腦和手機(jī)粥喜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來橘券,“玉大人额湘,你說我怎么就攤上這事≡加簦” “怎么了缩挑?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鬓梅。 經(jīng)常有香客問我供置,道長,這世上最難降的妖魔是什么绽快? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任芥丧,我火速辦了婚禮,結(jié)果婚禮上坊罢,老公的妹妹穿的比我還像新娘续担。我一直安慰自己,他們只是感情好活孩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布物遇。 她就那樣靜靜地躺著,像睡著了一般憾儒。 火紅的嫁衣襯著肌膚如雪询兴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天起趾,我揣著相機(jī)與錄音诗舰,去河邊找鬼。 笑死训裆,一個(gè)胖子當(dāng)著我的面吹牛眶根,可吹牛的內(nèi)容都是我干的蜀铲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼属百,長吁一口氣:“原來是場噩夢啊……” “哼记劝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起族扰,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤隆夯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后别伏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忧额,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年厘肮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睦番。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡类茂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出托嚣,到底是詐尸還是另有隱情巩检,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布示启,位于F島的核電站兢哭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏夫嗓。R本人自食惡果不足惜迟螺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舍咖。 院中可真熱鬧矩父,春花似錦、人聲如沸排霉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攻柠。三九已至球订,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辙诞,已是汗流浹背辙售。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留飞涂,地道東北人旦部。 一個(gè)月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓祈搜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親士八。 傳聞我的和親對象是個(gè)殘疾皇子容燕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355

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