Menu
CoddyTech

Mẫu Observer

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 89 trên 107.

Mẫu Observer xác định mối quan hệ một với nhiều giữa các đối tượng. Khi một đối tượng (chủ thể) thay đổi trạng thái, tất cả các đối tượng phụ thuộc vào nó (đối tượng quan sát) sẽ tự động nhận thông báo. Mẫu này phù hợp với các hệ thống sự kiện, thông báo hoặc mọi tình huống cần nhiều thành phần phản hồi trước các thay đổi.

Trong Go, chúng ta triển khai mẫu này bằng cách dùng một giao diện cho các đối tượng quan sát và một cấu trúc subject quản lý các lượt đăng ký:

type Observer interface {
    Update(event string)
}

type Subject struct {
    observers []Observer
}

func (s *Subject) Subscribe(o Observer) {
    s.observers = append(s.observers, o)
}

func (s *Subject) Notify(event string) {
    for _, observer := range s.observers {
        observer.Update(event)
    }
}

Bất kỳ kiểu nào triển khai giao diện Observer đều có thể đăng ký để nhận thông báo:

type EmailAlert struct{ Address string }

func (e EmailAlert) Update(event string) {
    fmt.Printf("Email to %s: %s\n", e.Address, event)
}

type Logger struct{}

func (l Logger) Update(event string) {
    fmt.Println("Log:", event)
}

Đối tượng chủ thể thông báo cho tất cả đối tượng quan sát mà không cần biết kiểu cụ thể của chúng:

subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})

subject.Notify("Order placed")
// Email to user@example.com: Order placed
// Log: Order placed

Mẫu này tách đối tượng được theo dõi khỏi các đối tượng quan sát, giúp bạn dễ dàng thêm kiểu đối tượng quan sát mới mà không cần sửa code hiện có. Mẫu này thường được dùng trong kiến trúc hướng sự kiện, framework giao diện đồ họa và hệ thống nhắn tin pub/sub.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống cảnh báo giá cổ phiếu bằng mẫu Observer! Bạn sẽ tạo một hệ thống để nhiều dịch vụ cảnh báo tự động nhận thông báo mỗi khi giá cổ phiếu thay đổi, rất phù hợp để minh họa cách các observer phản ứng với sự kiện mà đối tượng được theo dõi không cần biết kiểu cụ thể của chúng.

Bạn sẽ tổ chức code trong ba tệp:

  • observer.go: Định nghĩa interface observer và các kiểu observer cụ thể.

    Tạo interface Observer có phương thức Update(stockName string, price float64) để nhận thông tin cổ phiếu khi giá thay đổi.

    Triển khai hai kiểu observer:

    • PriceAlert có trường Threshold (float64). Khi được cập nhật, nó trả về ALERT: [stockName] at $[price] crossed threshold $[threshold] nếu giá cao hơn ngưỡng, hoặc WATCHING: [stockName] at $[price] (threshold: $[threshold]) nếu giá thấp hơn ngưỡng
    • PriceLogger có trường LogName (string). Khi được cập nhật, nó trả về [logName] recorded [stockName]: $[price]

    Cả hai observer đều cần có phương thức trả về chuỗi phản hồi đã định dạng.

  • stock.go: Tạo đối tượng được theo dõi để quản lý các observer và thông báo cho chúng khi giá thay đổi.

    Tạo struct Stock với các trường Name (string), Price (float64) và một lát cắt observer. Thêm các phương thức sau:

    • Subscribe(o Observer) để thêm một observer
    • SetPrice(price float64) []string để cập nhật giá và thông báo cho tất cả observer, sau đó trả về một lát cắt chứa mọi phản hồi

    Đồng thời tạo hàm khởi tạo NewStock(name string, initialPrice float64) *Stock.

  • main.go: Thiết lập cổ phiếu cùng các observer và mô phỏng những lần thay đổi giá.

    Đọc tên cổ phiếu và giá ban đầu. Sau đó đọc số lượng observer cần tạo. Với mỗi observer, đọc kiểu của nó (alert hoặc logger) và cấu hình (ngưỡng cho cảnh báo, tên log cho logger). Đăng ký từng observer với cổ phiếu.

    Cuối cùng, đọc giá mới, cập nhật cổ phiếu và in phản hồi của từng observer trên một dòng riêng.

Đầu vào sẽ có định dạng như sau:

  • Dòng 1: Tên cổ phiếu
  • Dòng 2: Giá ban đầu
  • Dòng 3: Số lượng observer
  • Với mỗi observer: kiểu (alert hoặc logger), sau đó là giá trị cấu hình
  • Dòng cuối: Giá mới cần đặt

Định dạng tất cả giá với hai chữ số thập phân.

Ví dụ, với đầu vào:

GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50

Đầu ra của bạn sẽ là:

ALERT: GOOG at $160.50 crossed threshold $155.00
TradeLog recorded GOOG: $160.50
ALERT: GOOG at $160.50 crossed threshold $140.00

Với đầu vào:

AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25

Đầu ra của bạn sẽ là:

DailyLog recorded AAPL: $175.25
WATCHING: AAPL at $175.25 (threshold: $200.00)

Lưu ý rằng Stock không biết mình đang thông báo cho alert hay logger. Nó chỉ gọi Update trên từng observer. Cách tách rời này giúp bạn dễ dàng thêm kiểu observer mới mà không cần sửa code của Stock!

Tự làm thử

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Đọc tên cổ phiếu
	stockName, _ := reader.ReadString('\n')
	stockName = strings.TrimSpace(stockName)

	// Đọc giá ban đầu
	initialPriceStr, _ := reader.ReadString('\n')
	initialPrice, _ := strconv.ParseFloat(strings.TrimSpace(initialPriceStr), 64)

	// Đọc số lượng đối tượng quan sát
	numObserversStr, _ := reader.ReadString('\n')
	numObservers, _ := strconv.Atoi(strings.TrimSpace(numObserversStr))

	// TODO: Tạo Stock mới bằng hàm khởi tạo NewStock

	// TODO: Lặp qua từng đối tượng quan sát
	for i := 0; i < numObservers; i++ {
		// Đọc loại đối tượng quan sát ("alert" hoặc "logger")
		observerType, _ := reader.ReadString('\n')
		observerType = strings.TrimSpace(observerType)

		// Đọc giá trị cấu hình
		configValue, _ := reader.ReadString('\n')
		configValue = strings.TrimSpace(configValue)

		// TODO: Tạo đối tượng quan sát phù hợp theo loại
		// - Với "alert": chuyển configValue thành ngưỡng kiểu float64, tạo PriceAlert
		// - Với "logger": dùng configValue làm LogName, tạo PriceLogger
		// TODO: Đăng ký đối tượng quan sát với cổ phiếu
	}

	// Đọc giá mới
	newPriceStr, _ := reader.ReadString('\n')
	newPrice, _ := strconv.ParseFloat(strings.TrimSpace(newPriceStr), 64)

	// TODO: Đặt giá mới và lấy các phản hồi

	// TODO: In mỗi phản hồi trên một dòng riêng
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Go online