什么是觀察者模式?
網(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ù)量的變化
以上看靠,完