Observer Pattern là một kỹ thuật trong đó có 2 loại đối tượng: Publisher (phát thông báo) và Observer (hay Subscriber) quan sát, lắng nghe sự kiện. Ưu điểm của Observer Patter là chuẩn hoá thêm hay bớt Observer và thông báo đến danh sách Observer khi có thay đổi qua interface. Trong bài này tôi sẽ triển khi Observer Pattern bằng Golang. Link tải mã nguồn demo ở đây
Tình huống ví dụ
Mùa giải 2020-2021, nhiều fan hâm mộ và cả những tay cá độ bóng đá quan tâm xem liệu Manchester United có vô địch giải Ngoại Hạng Anh trong cuộc đua với Manchester City. Họ quan tâm đến điểm cộng dồn sau mỗi trận đấu. Thắng được 3, Hoà được 1, thua không được điểm nào. Xét trong Observer Pattern thì Manchester United sẽ là Publisher, còn fan hâm mộ và các tay cá độ là Observer.
Để triển khai Observer Pattern, hãy định nghĩa 2 interface chính mà Publisher và Observer sẽ cần tuân thủ (implements)
Publisher Interface
Định nghĩa interface
type Publisher interface {
RegisterObserver(o Observer)
RemoveObserver(o Observer)
NotifyObserver()
}
Publisher Implementation
rồi sau đó là struct FootBallClub tuân thủ interface này. Đặc biệt FootbalClub
cần phải chứa danh sách các Observer observerList []Observer
// FootballClub đối tượng sẽ phát đi thông báo thay đổi: Publisher
type FootballClub struct {
name string //Tên câu lạc bộ bóng đá
point int //Khi point thay đổi giá trị cần phát đi thông báo cho tất cả các đối tượng tuân thủ Observer
observerList []Observer //Danh sách các đối tượng quan sát được trừu tượng hoá qua interface Observer
}
// Hàm constructor của FootballClub
func NewFootballClub(name string) *FootballClub {
return &FootballClub{
name: name,
point: 0,
observerList: make([]Observer, 0),
}
}
// Đăng ký một đối tượng sẽ nhận thông báo thay đổi
func (fbc *FootballClub) RegisterObserver(o Observer) {
fbc.observerList = append(fbc.observerList, o)
}
// Loại bỏ đối tượng ra khỏi danh sách thông báo dạng slice
func (fbc *FootballClub) RemoveObserver(o Observer) {
found := false
i := 0
for ; i < len(fbc.observerList); i++ {
if fbc.observerList[i] == o {
found = true
break
}
}
if found {
fbc.observerList = append(fbc.observerList[:i], fbc.observerList[i+1:]...)
}
}
// NotifyObserver thông báo đến tất cả các Observer trong danh sách
func (fbc *FootballClub) NotifyObserver() {
for _, observer := range fbc.observerList {
observer.Update(fbc.point)
}
}
// SetPoint thay đổi giá trị đồng thời thông báo đến tất cả các Observer
func (fbc *FootballClub) SetPoint(point int) {
fbc.point = point
fbc.NotifyObserver()
}
Hãy chú ý đến hàm func (fbc *FootballClub) SetPoint(point int)
và hàm func (fbc *FootballClub) NotifyObserver()
. Khi điểm số của câu lạc bộ bóng đá thay đổi tất cả các phần tử trong slice observerList
Observer Interface
Định nghĩa interface Observer có một mẫu phương thức Update. Publisher sẽ gọi đến observer.Update(value)
khi cần thông báo
type Observer interface {
//Thông tin cần cập nhật ở đây là giá trị int
Update(value int)
}
Observer Implementation
Giờ chúng ta triển khai chi tiết 2 struct Fan và Gambler cùng tuân thủ interface Observer
Fan
type Fan struct {
name string
}
func (u *Fan) Update(point int) {
fmt.Printf("%s nhận được cập nhật : %d\n", u.name, point)
}
Gambler
type Gambler struct {
name string
}
func (g *Gambler) Update(value int) {
if value > 90 {
fmt.Println("Thắng cá độ to rồi")
} else {
fmt.Println("Thua cá độ rồi")
}
}
Ứng dụng chạy thử
func main() {
FootballClub := NewFootballClub("Manchester United")
tom := Fan{name: "Tom"}
bob := Fan{name: "Bob"}
alice := Fan{name: "Alice"}
mike := Fan{name: "Mike"}
trumCado := Gambler{name: "Trùm cá độ"}
FootballClub.RegisterObserver(&tom)
FootballClub.RegisterObserver(&bob)
FootballClub.RegisterObserver(&alice)
FootballClub.RegisterObserver(&mike)
FootballClub.RegisterObserver(&trumCado)
// Mọi Fan đều nhận được thông báo khi FootballClub thay đổi giá trị
fmt.Println("------First update")
FootballClub.SetPoint(89)
//Loại bỏ mike ra khỏi danh sách nhận thông báo
FootballClub.RemoveObserver(&mike)
fmt.Println("------Second update")
//Mọi người ngoại trừ mike đều nhận được thông báo
FootballClub.SetPoint(92)
}
Sẽ cho kết quả trong terminal như sau:
------First update
Tom nhận được cập nhật : 89
Bob nhận được cập nhật : 89
Alice nhận được cập nhật : 89
Mike nhận được cập nhật : 89
Thua cá độ rồi
------Second update
Tom nhận được cập nhật : 92
Bob nhận được cập nhật : 92
Alice nhận được cập nhật : 92
Thắng cá độ to rồi
Kết luận
Observer Pattern là một kỹ thuật giúp Publisher và Observer không bị ràng buộc bởi kiểu cụ thể (concrete type) mà chỉ cần thông qua 2 interface là Publisher
và Observer
. Kỹ thuật này có liên quan họ hàng đến những kỹ thuật sau này là Reactive Programming. Định nghĩa của Reactive Programming như sau reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change.
Mã nguồn tôi ví dụ ở trên chỉ chạy đơn luồng (single thread). Trong Golang chúng ta có thể sử dụng channel, buffer để publish event và subscribe event khi ở 2 coroutine khác nhau.
Bình luận