「設(shè)計(jì)模式(二) - 觀察者模式」

「設(shè)計(jì)模式(二) - 觀察者模式」

一能岩、回復(fù)TD退訂

日常生活中募强,這種短信無處不在株灸,各種廣告,在互聯(lián)網(wǎng)高速發(fā)展的今天钻注,個(gè)人信息可以說是透明的。沒有任何隱私可言配猫,類似這種通知其實(shí)跟我們開發(fā)過程使用的觀察者模式(Observer Pattern)如出一轍幅恋。更貼切的像初中時(shí)代,英語學(xué)習(xí)的周報(bào)訂閱泵肄,一個(gè)班級(jí)大部分還是會(huì)訂閱捆交,基本上一周一期。這個(gè)算是比較典型的觀察者模式也即-發(fā)布-訂閱腐巢∑纷罚可以這樣理解,Publishers+Subscribers=Obeserver Pattern冯丙。像這種一對(duì)多的關(guān)系肉瓦,一個(gè)對(duì)象狀態(tài)的改變,所有訂閱它的對(duì)象都會(huì)被通知到并進(jìn)行自己的一些操作可以用觀察者模式來解釋。

二泞莉、觀察者模式 Obeserver Pattern

對(duì)象之間存在像這種一對(duì)多的依賴關(guān)系哪雕,當(dāng)被訂閱的對(duì)象(Publishers)狀態(tài)發(fā)生變化時(shí),訂閱者們(Subscribers)會(huì)收到相應(yīng)的通知并作出相應(yīng)的操作(更新自身的狀態(tài)或行為操作)鲫趁,即為觀察者模式斯嚎,是一種對(duì)象行為型模式。

三挨厚、組成部分

以上述為例:

  • 需要抽象的主題(Subject)堡僻,英語周報(bào)就是這個(gè)主題,直觀思考就可以理解疫剃,需要存儲(chǔ)所有訂閱了的學(xué)生(Observers)钉疫,以便確保每個(gè)訂閱的學(xué)生都能收到。需要可以刪除不在訂閱的學(xué)生(Observer)慌申,那么同樣的也可以新增想要訂閱的學(xué)生(Observer)陌选。

    發(fā)布者Subject 需要持有所有的訂閱者,并提供新增蹄溉、刪除訂閱的方法咨油,通知訂閱者們自身狀態(tài)改變的抽象方法(Notify)

  • 需要抽象的訂閱者(Observser),學(xué)生在這里可以作為充當(dāng)訂閱者的角色柒爵,可以抽象為Observer役电,更加通用一點(diǎn),老師同樣可以訂閱棉胀,其他有需要的人一樣可以訂閱法瑟。Observer即可理解為角色的抽象。

    訂閱者Observer唁奢,抽象的接口或抽象類(一般常見的為接口)霎挟,提供更新自己行為、或?qū)傩缘某橄蠓椒?/p>

  • 需要具體訂閱者實(shí)現(xiàn)類(Concrete Observer)麻掸,前面提到了酥夭,學(xué)生僅僅是眾多訂閱類型的一種,任何有需要的人都可以訂閱脊奋,僅需實(shí)現(xiàn)Observer接口即可熬北,設(shè)計(jì)的易擴(kuò)展性。

  • 同樣的具體主題實(shí)現(xiàn)者(Concrete Subject)诚隙,是對(duì)Subject的具體實(shí)現(xiàn)讶隐,好處不用多說,這也是為什么我們?cè)趯W(xué)習(xí)設(shè)計(jì)模式之初首先需要理解六種基本的設(shè)計(jì)原則久又。抽象不依賴實(shí)現(xiàn)細(xì)節(jié)巫延,細(xì)節(jié)應(yīng)該依賴抽象效五,抽象約束了細(xì)節(jié)使細(xì)節(jié)更規(guī)范可控

  • 結(jié)構(gòu)圖:

圖片來自網(wǎng)絡(luò).png
四烈评、代碼實(shí)現(xiàn)
1.設(shè)計(jì)一個(gè)價(jià)格變動(dòng)系統(tǒng)

水果店里的水果眾多火俄,應(yīng)季水果通常很貴,像現(xiàn)在這個(gè)季節(jié)的車?yán)遄?還沒實(shí)現(xiàn)車?yán)遄幼杂??)讲冠,不易保存的水果瓜客,草莓等等,價(jià)格統(tǒng)一管理并下發(fā)到各個(gè)門店竿开。

  • 主題Subject谱仪,也即是發(fā)布者
/**
 * Created by Sai
 * on: 10/01/2022 11:41.
 * Description:
 */
public abstract class Subject {

  private final Logger logger = Logger.getLogger(Subject.class.getName());
  /** 保存訂閱者對(duì)象集合 */
  private final List<Observer> observerList = new CopyOnWriteArrayList<>();

  /** 新增訂閱者Observer */
  public boolean attach(Observer observer) {
    logger.info(observer + "添加成功");
    return observerList.add(observer);
  }

  /** 解除已經(jīng)綁定的訂閱者的關(guān)系 */
  public boolean detach(Observer observer) {
    logger.info(observer + "解綁成功");
    return observerList.remove(observer);
  }

  /** 通知更新到Observers */
  protected void notifyObservers() {
    if (observerList.isEmpty()) {
      return;
    }
    for (Observer observer : observerList) {
      observer.priceChanged(this);
    }
  }
}
  • 具體的實(shí)現(xiàn)類-如車?yán)遄?/li>
/**
 * Created by Sai
 * on: 10/01/2022 12:16.
 * Description:
 */
public class Cherry extends Subject {
  /** 價(jià)格 */
  private long price;

  /** 名稱 */
  private String name;

  public Cherry(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public long getPrice() {
    return price;
  }

  public void setPrice(long price) {
    /** 當(dāng)價(jià)格發(fā)生改變通知到所有門店 */
    this.price = price;
    notifyObservers();
  }

  @Override
  public String toString() {
    return "Cherry{" + "price=" + price + ", name='" + name + '\'' + '}';
  }
}
  • 定義觀察者Observer接口
/**
 * Created by Sai
 * on: 10/01/2022 11:34.
 * Description:
 */
public interface Observer {
    //通知的是主題的一些信息
    void priceChanged(Subject subject);
}
  • 具體實(shí)現(xiàn)類-門店Store
/**
 * Created by Sai
 * on: 10/01/2022 12:22.
 * Description:
 */
public class Store implements Observer {

    private String storeName;

    public Store(String storeName) {
        this.storeName = storeName;
    }

    public String getStoreName() {
        return storeName;
    }

    public void setStoreName(String storeName) {
        this.storeName = storeName;
    }

    private void show(Subject subject) {
        System.out.println(this.storeName + subject.toString());
    }

    @Override
    public void priceChanged(Subject subject) {
        System.out.println(this.storeName);
        System.out.println("Price Changed - Refreshing price");
        show(subject);
        System.out.println("---------------------------------------->");
    }
}
  • 測(cè)試用例
/**
 * Created by Sai
 * on: 10/01/2022 12:27.
 * Description:
 */
public class Demo {

  public static void main(String[] args) {
    Cherry cherry = new Cherry("車?yán)遄?);
    Store one = new Store("門店一");
    Store two = new Store("門店二");

    cherry.attach(one); cherry.attach(two);
    cherry.setPrice(100);
    //價(jià)格漲了
    cherry.setPrice(200);
    //門店一倒閉了
    cherry.detach(one);
    cherry.setPrice(99);
  }
}
  • 打印信息
信息: com.observer.sai.Store@2f92e0f4添加成功
1月 10, 2022 1:01:22 下午 com.observer.sai.Subject attach
信息: com.observer.sai.Store@4f3f5b24添加成功
1月 10, 2022 1:01:22 下午 com.observer.sai.Subject detach
信息: com.observer.sai.Store@2f92e0f4解綁成功
門店一
Price Changed - Refreshing price
門店一Cherry{price=100, name='車?yán)遄?}
---------------------------------------->
門店二
Price Changed - Refreshing price
門店二Cherry{price=100, name='車?yán)遄?}
---------------------------------------->
門店一
Price Changed - Refreshing price
門店一Cherry{price=200, name='車?yán)遄?}
---------------------------------------->
門店二
Price Changed - Refreshing price
門店二Cherry{price=200, name='車?yán)遄?}
---------------------------------------->
門店二
Price Changed - Refreshing price
門店二Cherry{price=99, name='車?yán)遄?}
---------------------------------------->
Process finished with exit code 0
2.一些思考

門店管理系統(tǒng),實(shí)現(xiàn)了水果價(jià)格變動(dòng)時(shí)動(dòng)態(tài)的下發(fā)信息到各個(gè)門店下面否彩,這個(gè)動(dòng)態(tài)的聯(lián)動(dòng)關(guān)系疯攒;只要主題對(duì)象的狀態(tài)或者行為發(fā)生改變。那么訂閱者們感知到這個(gè)變化相應(yīng)的作出自身的行為列荔。雖然有一定的耦合敬尺,但也是抽象層面的,耦合程度還是比較低的贴浙,擴(kuò)展性得到了保證砂吞。設(shè)計(jì)的初衷也僅僅就為了解決這幾個(gè)問題:降低耦合,提高內(nèi)聚崎溃、擴(kuò)展性蜻直、層次結(jié)構(gòu)性。

  • 降低了發(fā)布者(Publishers)與訂閱者(Subscribers)直接的耦合程度袁串,抽象間耦合概而。
  • 實(shí)現(xiàn)了發(fā)布者與訂閱者之間的動(dòng)態(tài)聯(lián)動(dòng),訂閱者對(duì)發(fā)布者行為的改變作出自身的改變囱修。

當(dāng)然缺點(diǎn)也是很明顯的赎瑰,從例子中就可以看出,隨著門店的擴(kuò)張破镰,系統(tǒng)的調(diào)用深度也會(huì)增加餐曼。其次門店的閉店來不及解綁的情況下,也導(dǎo)致了一些無謂的通知啤咽,增加系統(tǒng)的負(fù)擔(dān)晋辆。

  • 即時(shí)訂閱與即時(shí)解綁渠脉,前者會(huì)導(dǎo)致通知不到位宇整,錯(cuò)過重要的信息;而后者則會(huì)造成無謂的通知芋膘。
  • 如果存在互相依賴的情況下鳞青,那么會(huì)出現(xiàn)死循環(huán)的情況霸饲,當(dāng)然這種場(chǎng)景下,首先應(yīng)該考慮的到的是觀察者模式是否真的適合在此時(shí)使用臂拓?
五厚脉、實(shí)際問題

在題主目前工作內(nèi)容中涉及到使用Observer Pattern的場(chǎng)景還是比較多的,其中購物車模塊業(yè)務(wù)胶惰,當(dāng)商品被加到購物車中:總價(jià)格發(fā)生變化傻工、商品數(shù)目紅點(diǎn)數(shù)量信息改變、商品庫存孵滞、單個(gè)商品被選購的次數(shù)等都發(fā)生了變動(dòng)中捆。反之刪除商品同樣。

public interface OnCartObserver {
  ...
  void onDeleteProduct(int position, int needUpdateCount);
  void onUpdateProduct(int position, CartProductVO product);
  void onCartClear();
  //備注的更新
  void onRemarkUpdate(String[] remarks);
  ...
}

public abstract class BaseOnCartObserver implements OnCartObserver {
  ...
  @Override
  public void onDeleteProduct(int position, int needUpdateCount) {

  }
  
  @Override
  public void onCartClear() {
    
  }
  
  @Override
  public void onRemarkUpdate(String[] remarks) {
    
  }
  
  @Override
  public void onUpdateProduct(int position, CartProductVO product) {
    
  }
  ...
}

//調(diào)用采用了匿名內(nèi)部類的形式坊饶,并沒有嚴(yán)格的遵循觀察者模式UML實(shí)現(xiàn)
public class CartGoodsFragment extends BaseFragment {
  private final OnCartObserver onCartObserver = new BaseOnCartObserver() {
    //行為的更新
  }
  
  @Override
  public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    XX.provideController.addObserver(onCartObserver);
  }
  
  @Override
  public void onDestroy() {
    super.onDestroy();
    XX.provideController.removeObserver(onCartObserver);
  }
}

嚴(yán)格意義上這并不是標(biāo)準(zhǔn)的觀察者模式泄伪,但是觀察者模式的本質(zhì)-一對(duì)多,主題狀態(tài)改變時(shí)匿级,依賴者會(huì)即時(shí)感知并作出反應(yīng)蟋滴,通俗的講就是出發(fā)聯(lián)動(dòng)效果。

還是回到設(shè)計(jì)的本質(zhì)痘绎,設(shè)計(jì)的初衷降低耦合津函、降低復(fù)雜度、提高擴(kuò)展性简逮,設(shè)計(jì)沒有定式球散,如果完全照搬不僅會(huì)適得其反,其次也可能使項(xiàng)目變得四不像散庶。為了設(shè)計(jì)而設(shè)計(jì)并不可取蕉堰,重要的還是細(xì)想的理解。實(shí)際業(yè)務(wù)與功能千變?nèi)f化悲龟,對(duì)業(yè)務(wù)的抽象能力屋讶,思考才是最重要,設(shè)計(jì)模式僅僅是把思路轉(zhuǎn)化為實(shí)現(xiàn)細(xì)節(jié)的手段须教。

六皿渗、該何時(shí)使用
  • 抽象模型之間存在一對(duì)多這種關(guān)系時(shí),并且主體的改變會(huì)引起其他依賴者的改變時(shí)轻腺。
  • 需要動(dòng)態(tài)聯(lián)動(dòng)的關(guān)系乐疆,且構(gòu)建盡可能低的耦合度的系統(tǒng)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載贬养,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者挤土。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市误算,隨后出現(xiàn)的幾起案子仰美,更是在濱河造成了極大的恐慌迷殿,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咖杂,死亡現(xiàn)場(chǎng)離奇詭異庆寺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)诉字,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門懦尝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人壤圃,你說我怎么就攤上這事导披。” “怎么了埃唯?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵撩匕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我墨叛,道長(zhǎng)止毕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任漠趁,我火速辦了婚禮扁凛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闯传。我一直安慰自己谨朝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布甥绿。 她就那樣靜靜地躺著字币,像睡著了一般。 火紅的嫁衣襯著肌膚如雪共缕。 梳的紋絲不亂的頭發(fā)上洗出,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音图谷,去河邊找鬼翩活。 笑死,一個(gè)胖子當(dāng)著我的面吹牛便贵,可吹牛的內(nèi)容都是我干的菠镇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼承璃,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼利耍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤堂竟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后玻佩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體出嘹,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年咬崔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了税稼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垮斯,死狀恐怖郎仆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情兜蠕,我是刑警寧澤扰肌,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站熊杨,受9級(jí)特大地震影響曙旭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晶府,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一桂躏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧川陆,春花似錦剂习、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至尸曼,卻和暖如春猾昆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骡苞。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工垂蜗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人解幽。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓贴见,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親躲株。 傳聞我的和親對(duì)象是個(gè)殘疾皇子片部,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

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