發佈-訂閱模式
在軟件架構中,發佈訂閱是一種消息範式,消息的發送者(稱爲發佈者)不會將消息直接發送給特定的接收者(稱爲訂閱者)。而是將發佈的消息分爲不同的類別,無需瞭解哪些訂閱者(如果有的話)可能存在。同樣的,訂閱者可以表達對一個或多個類別的興趣,只接收感興趣的消息,無需瞭解哪些發佈者(如果有的話)存在。
舉個報紙的例子:
還是得說一下報紙,有人說報紙不就是觀察者模式,那得有多少觀察者和主題?一張報紙那麼多板塊,訂報紙的人那麼多,難道要一個人一個人的通知,顯然不現實。如果在記者(編輯)和讀者之間加了一個載體報紙,那麼這還是觀察者模式嗎?
無數的編輯將新聞發到報設,報社在將信息整合到報紙同意發送到讀者手中,顯然這不是觀察者模式,觀察者模式中,觀察者和主題有着很強的耦合性,而在這裏顯然記者不認識讀者,讀者也不能通過報紙直接和編輯通信,這就是發佈者訂閱者模式,簡單來說和發佈者的區別就是多了一家報社。興許我這樸實的例子並不能讓你看明白,我們看一下國外的大佬怎麼說?
觀察者模式和發佈訂閱模式有什麼區別?
之前我的回答是《Head First設計模式》裏講的:Publishers + Subscribers = Observer Pattern
而且GoF也說只是別稱。但是衆說紛紜,可能看問題的觀點不同,前人是大佬,後人也要用發展性的眼觀看待,我麼就來看看這兩種設計模式到底有什麼區別。
首先我們來重新來回顧一下觀察者模式:
觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新。而觀察者模式屬於行爲型模式,行爲型模式關注的是對象之間的通訊,觀察者模式就是觀察者和被觀察者之間的通訊。
由上圖可以詳細的看出觀察者和被觀察者是密切聯繫的。
我們再來看看發佈者-訂閱者模式
在“發佈者-訂閱者”模式中,稱爲發佈者的消息發送者不會將消息編程爲直接發送給稱爲訂閱者的特定接收者。這意味着發佈者和訂閱者不知道彼此的存在。存在第三個組件,稱爲代理或消息代理或事件總線,它由發佈者和訂閱者都知道,它過濾所有傳入的消息並相應地分發它們。換句話說,pub-sub是用於在不同系統組件之間傳遞消息的模式,而這些組件不知道關於彼此身份的任何信息。經紀人如何過濾所有消息?實際上,有幾個消息過濾過程。最常用的方法有:基於主題和基於內容的。
我們放幾張圖,方便理解:
總結出的差異
- 在觀察者模式中,觀察者知道主題,主題也維護觀察者的記錄。而在發佈者/訂閱者中,發佈者和訂閱者不需要彼此瞭解。他們只是在消息隊列或代理的幫助下進行通信。
- 在發佈者/訂閱者模式中,與觀察者模式相反,組件是鬆散耦合的。
- 觀察者模式大多數是以同步方式實現的,即,當某個事件發生時,主題調用其所有觀察者的適當方法。的發行者/訂戶圖案在一個實施大多異步方式(使用消息隊列)。
- 觀察者模式需要在單個應用程序地址空間中實現。另一方面,發佈者/訂閱者模式更多地是跨應用程序模式。
儘管這些模式之間存在差異,但有些人可能會說Publisher-Subscriber模式是Observer模式的變體,因爲它們之間在概念上相似。而且這根本沒有錯。無需認真對待差異。它們是相似的,不是嗎?
注: 上文參考地址:https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c
優點:
- 鬆耦合
發佈者與訂閱者鬆耦合,甚至不需要知道它們的存在。由於主題纔是關注的焦點,發佈者和訂閱者可以對系統拓撲結構保持一無所知。各自繼續正常操作而無需顧及對方。 - 可擴展性
通過並行操作,消息緩存,基於樹或基於網絡的路由等技術,發佈/訂閱提供了比傳統的客戶端–服務器更好的可擴展性。
缺點:
- 發佈/訂閱系統最嚴重的問題是其主要優點的副作用:發佈者解耦訂閱者。
- 消息交付問題:發佈/訂閱系統必須仔細設計,才能提供特定的應用程序可能需要的更強大的系統性能,因爲鬆耦合,無論訂閱者是否正常收到發佈內容,訂閱器都會停止發送。
- 訂閱器中的內容隨着發佈者使用者的增加服務器的負載,對中介服務器是極大的考驗!
UML圖
具體實現
別詬病我的中文寫代碼,爲了看的更清楚一點,因爲不好理解,我看了好久的!
1.發佈者接口
package 發佈者訂閱者模式;
public interface I發佈者接口<M> {
public void publish(訂閱器 subscribePublish, M message, boolean isInstantMsg);
//使用哪個訂閱器,發佈什麼信息
}
2.訂閱者者接口
package 發佈者訂閱者模式;
public interface I訂閱者接口<M> {
public void subcribe(訂閱器 subscribePublish);
//從哪個訂閱器訂閱
public void unSubcribe(訂閱器 subscribePublish);
//取消訂閱
public void update(String publisher, M message);
//更新操作,參考觀察者模式
}
3.實際發佈者1
package 發佈者訂閱者模式;
public class Ac實際發佈者<M> implements I發佈者接口<M> {
private String name;
public Ac實際發佈者(String name) {
super();
this.name = name;
}
public void publish(訂閱器 subscribePublish, M message, boolean isInstantMsg) {
subscribePublish.publish(this.name, message, isInstantMsg);
}
}
4.實際訂閱者1
package 發佈者訂閱者模式;
public class Ac實際訂閱者<M> implements I訂閱者接口<M> {
public String name;
public Ac實際訂閱者(String name) {
super();
this.name = name;
}
public void subcribe(訂閱器 subscribePublish) {
subscribePublish.subcribe(this);
}
public void unSubcribe(訂閱器 subscribePublish) {
subscribePublish.unSubcribe(this);
}
public void update(String publisher, M message) {
System.out.println(this.name + "收到" + publisher + "發來的消息:" + message.toString());
}
}
5.訂閱信息(報紙?RSS?微信公衆號?都是)
package 發佈者訂閱者模式;
public class 發佈的信息<M> {
private String publisher;
private M m;
public 發佈的信息(String publisher, M m) {
this.publisher = publisher;
this.m = m;
}
public String getPublisher() {
return publisher;
}
public void setPublisher(String publisher) {
this.publisher = publisher;
}
public M getMsg() {
return m;
}
public void setMsg(M m) {
this.m = m;
}
}
6.訂閱器
package 發佈者訂閱者模式;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class 訂閱器<M> {
//訂閱器名稱
private String name;
//訂閱器隊列容量
final int QUEUE_CAPACITY = 300; //最大訂閱器數量
//訂閱器存儲隊列
private BlockingQueue<發佈的信息> queue = new ArrayBlockingQueue<發佈的信息>(QUEUE_CAPACITY);
//訂閱者
private List<I訂閱者接口> subcribers = new ArrayList<I訂閱者接口>();
public 訂閱器(String name) {
this.name = name;
}
public void publish(String publisher, M message, boolean isInstantMsg) {
if (isInstantMsg) {
update(publisher, message);
return;
}
發佈的信息<M> m = new 發佈的信息<M>(publisher, message);
if (!queue.offer(m)) {
update();
}
}
public void subcribe(I訂閱者接口 subcriber) {
subcribers.add(subcriber);
}
public void unSubcribe(I訂閱者接口 subcriber) {
subcribers.remove(subcriber);
}
public void update() {
發佈的信息 m = null;
while ((m = queue.peek()) != null) {
this.update(m.getPublisher(), (M) m.getMsg());
}
}
public void update(String publisher, M Msg) {
for (I訂閱者接口 subcriber : subcribers) {
subcriber.update(publisher, Msg);
}
}
}
7.測試
package 發佈者訂閱者模式;
public class MainTest {
public static void main(String[] args) {
訂閱器<String> subscribePublish = new 訂閱器<String>("報紙訂閱平臺");
I發佈者接口<String> publisher1 = new Ac實際發佈者<String>("紐約時報");
I訂閱者接口<String> subcriber1 = new Ac實際訂閱者<String>("特朗普");
I訂閱者接口<String> subcriber2 = new Ac實際訂閱者<String>("普京");
subcriber1.subcribe(subscribePublish);
subcriber2.subcribe(subscribePublish);
publisher1.publish(subscribePublish, "美國新型冠狀病毒爆發的原因", true);
publisher1.publish(subscribePublish, "竟然是", true);
publisher1.publish(subscribePublish, "川普不作爲", false);
}
}
寫在最後:
我叫風骨散人,名字的意思是我多想可以不低頭的自由生活,可現實卻不是這樣。家境貧寒,總得向這個世界低頭,所以我一直在奮鬥,想改變我的命運
給親人好的生活,希望同樣被生活綁架的你
可以通過自己的努力改變現狀,深知成年人的世界裏沒有容易二字。目前是一名在校大學生,預計考研,熱愛編程,熱愛技術,喜歡分享,知識無界,希望我的分享可以幫到你!
如果有什麼想看的,可以私信我,如果在能力範圍內,我會發布相應的博文!
謝謝大家的閱讀!😘