<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們先來簡單學習一下用 Go 實現觀察者設計模式,給怎麼實現事件驅動程式設計、事件源這些模式做個鋪墊。主要也是我也老沒看設計模式了,一起再複習一下。以前看的設計模式教學都是 Java 的,這次用 Go 實現一番。
咱們先來看一下觀察者模式的概念,我儘量加一些自己的理解,讓它變成咱們都能理解的大俗話:
觀察者模式 (Observer Pattern),定義物件間的一種一對多依賴關係,使得每當一個物件狀態發生改變時,其相關依賴物件皆得到通知,依賴物件在收到通知後,可自行呼叫自身的處理程式,實現想要乾的事情,比如更新自己的狀態。
釋出者對觀察者唯一瞭解的是它實現了某個介面(觀察者介面)。這種鬆散耦合的設計最大限度地減少了物件之間的相互依賴,因此使我們能夠構建靈活的系統來處理主體的變化。
上面這段話看完,相信幾乎對於理解觀察者模式能起到的作用微乎其微,類似於現實職場里加班對專案進度起到的作用一樣,加班的時候誰還沒打過幾把王者榮耀,嘿。下面我用自己的理解再給你們嘮一下。
觀察者模式也經常被叫做釋出 - 訂閱(Publish/Subscribe)模式、上面說的定義物件間的一種一對多依賴關係,一 - 指的是釋出變更的主體物件,多 - 指的是訂閱變更通知的訂閱者物件。
釋出的狀態變更資訊會被包裝到一個物件裡,這個物件被稱為事件,事件一般用英語過去式的語態來命名,比如使用者註冊時,使用者模組在使用者建立好後釋出一個事件 UserCreated 或者 UserWasCreated 都行,這樣從名字上就能看出,這是一個已經發生過的事件。
事件釋出給訂閱者的過程,其實就是遍歷一下已經註冊的事件訂閱者,逐個去呼叫訂閱者實現的觀察者介面方法,比如叫 handleEvent 之類的方法,這個方法的引數一般就是當前的事件物件。
至於很多人會好奇的,事件的處理是不是非同步的?主要看我們的需求是什麼,一般情況下是同步的,即釋出事件後,觸發事件的方法會阻塞等到全部訂閱者返回後再繼續,當然也可以讓訂閱者的處理非同步執行,完全看我們的需求。
大部分場景下其實是同步執行的,單體架構會在一個資料庫事務裡持久化因為主體狀態變更,而需要更改的所有實體類。
微服務架構下常見的做法是有一個事件儲存,訂閱者接到事件通知後,會把事件先存到事件儲存裡,這兩步也需要在一個事務裡完成才能保證最終一致性,後面會再有其他執行緒把事件從事件儲存裡搞到訊息設施裡,發給其他服務,從而在微服務架構下實現各個位於不同服務的實體間的最終一致性。
所以觀察者模式,從程式效率上看,大多數情況下沒啥提升,更多的是達到一種程式結構上的解耦,讓程式碼不至於那麼難維護。
說了這麼多,我們再看下用 Go 怎麼實現最簡單的觀察者模式:
package main import "fmt" // Subject 介面,它相當於是釋出者的定義 type Subject interface { Subscribe(observer Observer) Notify(msg string) } // Observer 觀察者介面 type Observer interface { Update(msg string) } // Subject 實現 type SubjectImpl struct { observers []Observer } // Subscribe 新增觀察者(訂閱者) func (sub *SubjectImpl) Subscribe(observer Observer) { sub.observers = append(sub.observers, observer) } // Notify 釋出通知 func (sub *SubjectImpl) Notify(msg string) { for _, o := range sub.observers { o.Update(msg) } } // Observer1 Observer1 type Observer1 struct{} // Update 實現觀察者介面 func (Observer1) Update(msg string) { fmt.Printf("Observer1: %sn", msg) } // Observer2 Observer2 type Observer2 struct{} // Update 實現觀察者介面 func (Observer2) Update(msg string) { fmt.Printf("Observer2: %sn", msg) } func main(){ sub := &SubjectImpl{} sub.Subscribe(&Observer1{}) sub.Subscribe(&Observer2{}) sub.Notify("Hello") }
這就是 Go 實現觀察者模式的程式碼,實際應用的時候,一般會定義個事件匯流排 EventBus 或者事件分發器 Event Dispatcher,來管理事件和訂閱者間的關係和分發事件,它們兩個就是名不一樣,角色定位一樣。
下面看一下用 Go 怎麼實現事件匯流排。
下面我們實現一個支援以下功能的事件匯流排
這個程式碼不是我自己寫的,出處見程式碼註釋首行。
// 程式碼來自https://lailin.xyz/post/observer.html package eventbus import ( "fmt" "reflect" "sync" ) // Bus Bus type Bus interface { Subscribe(topic string, handler interface{}) error Publish(topic string, args ...interface{}) } // AsyncEventBus 非同步事件匯流排 type AsyncEventBus struct { handlers map[string][]reflect.Value lock sync.Mutex } // NewAsyncEventBus new func NewAsyncEventBus() *AsyncEventBus { return &AsyncEventBus{ handlers: map[string][]reflect.Value{}, lock: sync.Mutex{}, } } // Subscribe 訂閱 func (bus *AsyncEventBus) Subscribe(topic string, f interface{}) error { bus.lock.Lock() defer bus.lock.Unlock() v := reflect.ValueOf(f) if v.Type().Kind() != reflect.Func { return fmt.Errorf("handler is not a function") } handler, ok := bus.handlers[topic] if !ok { handler = []reflect.Value{} } handler = append(handler, v) bus.handlers[topic] = handler return nil } // Publish 釋出 // 這裡非同步執行,並且不會等待返回結果 func (bus *AsyncEventBus) Publish(topic string, args ...interface{}) { handlers, ok := bus.handlers[topic] if !ok { fmt.Println("not found handlers in topic:", topic) return } params := make([]reflect.Value, len(args)) for i, arg := range args { params[i] = reflect.ValueOf(arg) } for i := range handlers { go handlers[i].Call(params) } }
package eventbus import ( "fmt" "testing" "time" ) func sub1(msg1, msg2 string) { time.Sleep(1 * time.Microsecond) fmt.Printf("sub1, %s %sn", msg1, msg2) } func sub2(msg1, msg2 string) { fmt.Printf("sub2, %s %sn", msg1, msg2) } func TestAsyncEventBus_Publish(t *testing.T) { bus := NewAsyncEventBus() bus.Subscribe("topic:1", sub1) bus.Subscribe("topic:1", sub2) bus.Publish("topic:1", "test1", "test2") bus.Publish("topic:1", "testA", "testB") time.Sleep(1 * time.Second) }
毫不意外這個事件匯流排,只是個例子,咱也不能在專案開發裡使用,這篇文章咱們先搞清概念,我其實前兩天關注了下,沒有發現什麼好用的事件分發、事件匯流排的三方庫,好在實現起來也不難,後面我準備自己寫一個能用的到時候分享給大家,最起碼是在學習、練習專案裡能使用的吧。
今天給大家用大白話瞎嘮了一下觀察者模式的原理和實際怎麼應用,感覺文章的精髓主要在前半部分,可能有的不你還不能理解,後面我會再通過後續文章逐一解釋,其實這些都是事件驅動和事件源這些模式裡的基礎內容。
至於這次給出的程式碼,其實沒啥實戰意義,就是大家先了解一下。Go 裡邊關於事件驅動之類的內容,感覺不多,有 Spring 使用經驗的可以先看看 Spring 提供的@EventListener 註解,需要訂閱者非同步執行可以配合 @Async 註解使用,至於我上面說的需要保證事件釋出的主體和訂閱者的原子性持久化的話,則是通過@Transitional 和 @TransactionalEventListener 結合使用來實現。
以上就是Go語言設計模式之實現觀察者模式解決程式碼臃腫的詳細內容,更多關於Go 觀察者模式的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45