Jianwoo中的设计模式(2) — 观察者模式

什么是观察者模式?

网上有很多种对于观察者模式的描述,我简单的说个比喻大概就能理解:我们常用的QQ邮箱,有订阅功能,你订阅了一个邮箱,以后这个邮箱每次有什么新内容,都会主动通知到你的邮箱里,你并不需要主动去订阅账号去看内容。当然我们进入邮箱获取邮箱是一个主动请求接口的过程,而观察者模式是一个不需要主动就能获取通知的一种“订阅”关系,我订阅了你,以后你只要发送一个消息,我都能收到,有3个订阅者那就3个都能收到,所以观察者模式一般会有“注册订阅”和“取消订阅”的两个行为
我们在开发中有什么场景会运用到观察者模式呢,最常见的就是EventBus了,不过EventBus是通过注解来获取订阅者的回调方法,而我们在开发中一般是运用接口来获取回调。我们在Android中所用到的各种监听setOnclickListener,setOnLongClicklistener等也是观察者模式,只不过这里是一对一的观察者模式,多对一有没有呢,Android里那些addXXXListener就属于多对一的观察者模式了,追进源码你会发现内部是通过一个集合来维护这些“订阅者”,说了这么多我们来聊一聊简物中运用观察者模式的场景

简物中的观察者模式

简物中除了使用EventBus作为发布/订阅事件总线,也使用了自己写的观察者莫斯,简物是一个电商APP,自然有商品详情界面,而简物中的商品详情又有入口可以进入到别的商品,也就是可以无限打开多个商品详情,商品详情下方的购物车区域有一个商品数量显示,点击购物车可以进入购物车界面,旁边的添加购物车可以添加当前商品到购物车
那需求是什么呢?我可以打开多个详情界面,也可以进入购物车界面,我在任意一个界面执行了添加商品到购物车或者在购物车列表进行了加减商品数量或者删除了商品,都要立即更新商品数量到每个商品界面,如果我们不用观察者模式那我们能怎么实现呢,大概就是在返回Activity的时候在onResume生命周期方法的地方去主动调用获取购物车数量更新到界面,或者在当前界面执行了添加购物车的方法后同时也调用更新购物车数量的方法,这样做确实能实现功能,但是这样做并不优雅而且要衔接一堆代码,对于更新迭代也非常不便
对于程序中的功能,除非是涉及到算法或者硬件或者底层的东西,要实现一个常规应用功能并不是难与不难的问题,可能初级新手也能实现这个功能,运行的界面也是一模一样,但是对于我们而言,我们应该要想着如何让程序有更好的扩展性,让我们的代码有更深层次的结构意义,这样做不是为了突出与别人的不同,而是为了让程序更健壮
这种场景下的功能,如果把要更新购物车数量的地方作为观察者,把管理购物车增删改查结果的地方作为被观察者,那我们只要给他们绑定一个订阅与被订阅的关系,并且在我们添加、删除、修改购物车的接口均给被订阅者发布一个更新消息,那这样不是所有的订阅者都能收到购物车被更改的消息,然后拿到订阅者传给我们的数据进行更新
那我们怎么做呢,这里我直接使用java的Observer(订阅者)和Observable(发布者)来实现观察者模式,这是java直接封装好的通用的观察者模式模型,那我们怎么用呢,首先在UI逻辑类(要更新购物车数量的地方)实现Observer接口,让它成为一个观察者,并且实现未实现的方法

public class SaleDetailCategory extends Category implements Observer{

    /**
     * 购物车商品数量
     */
    @Bind(R.id.sale_number)
    TextView mSaleNumber;

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

    ...

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

然后我们来封装一个发布者类,那首先我大概说一下简物中是如何获取购物车数量,因为每个购物车实体都有购物车uuid(cart_uuid, String)以及购物车商品数量(goods_number, Integer),那计算购物车中商品数量的方法应该是将所有cart_uuid的goods_number加起来,因为要不断的修改对应购物车的数量,所以我用HashMap<String, Integer>来做,既然是更新购物车数量的包装类,那自然会有更新购物车列表、更新指定购物车数量、添加购物车商品、删除购物车商品、清空购物车商品(添加商品到订单)的操作方法,那我就直接将这个类贴出来,不做详细描述啦

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

    /**
     * 用于返回到购物车界面是否要自动刷新的一个标识
     */
    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();
    }

    /**
     * 获取购物车总数量
     * @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;
    }

    /**
     * 更新指定商品购物车数量
     * @param cart_uuid
     * @param number
     */
    public void updateCartNumber(String cart_uuid, int number){
        if(mShoppingCart.containsKey(cart_uuid)){
            mShoppingCart.put(cart_uuid, number);
        }
        notifyDataChanged();
    }

    /**
     * 获取购物车数量文案
     * @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();
    }

    /**
     * 清空购物车数量:提交订单到购物车|退出登录
     */
    public void clearShoppingCart(){
        mShoppingCart.clear();
        notifyDataChanged();
    }

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

    /**
     * 购物车状态改变
     */
    public void shoppingCartLoaded(){
        setShoppingCartStateChanged(false);
    }

    public boolean isShoppingCartStateChanged() {
        return mShoppingCartStateChanged;
    }

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

}

那我们现在只要在对应更新购物车接口回调的地方调用这个类的方法即可,我这里贴一个栗子吧

/**
 * Created by Barry on 2017/1/20.
 * 添加到购物车实现
 */
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{
                    /**
                     * 更新购物车数量
                     */
                    App.getInstance().getShoppingCartState().addShoppingCartSuccess(o.getItems().get(0));
                }catch (Exception e){
                    App.getInstance().getShoppingCartState().addShoppingCartSuccess(null);
                }
            }
        });
    }
}

那回调成功并且调用购物车数量更新包装类之后,我们的订阅者也能拿到消息,并且更新界面啦,怎么实现的呢

public class SaleDetailCategory extends Category implements Observer{

    /**
     * 购物车商品数量
     */
    @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);
        }
    }

最后贴一个实现的效果图,上传后发现,不但变的不流畅而且界面中1px的分割线都消失了…
注意观察GIF图中底部购物车数量栏目数量的变化

更新购物车数量

以上,完

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容