「設(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)圖:
四烈评、代碼實(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)。