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à PublisherObserver. 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.