옵저버 패턴
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 107개 중 89번째.
Observer 패턴은 객체 간의 일대다 관계를 정의합니다. 한 객체(주체)의 상태가 changes하면, all its 종속 객체(observer)가 자동으로 알림을 받습니다. 이는 event 시스템, 알림 또는 여러 구성 요소가 changes에 반응해야 하는 any 시나리오에 적합합니다.
Go에서는 observer를 위한 interface와 구독을 관리하는 subject 구조체를 사용하여 이 패턴을 구현합니다:
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)
}
}Observer 인터페이스를 구현하는 모든 타입은 알림을 받도록 구독할 수 있습니다:
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)
}subject는 구체적인 타입을 알지 못한 채 모든 observer에게 알립니다:
subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})
subject.Notify("Order placed")
// user@example.com으로 이메일: Order placed
// 로그: Order placed이 패턴은 주체와 옵서버를 분리하므로 기존 코드를 수정하지 않고도 새로운 옵서버 유형을 쉽게 추가할 수 있습니다. 이벤트 기반 아키텍처, GUI 프레임워크, pub/sub 메시징 시스템에서 흔히 사용됩니다.
챌린지
쉬움Observer 패턴을 사용하여 주가 알림 시스템을 만들어 봅시다! 주가가 변할 때마다 여러 알림 서비스가 자동으로 알림을 받는 시스템을 만들게 됩니다. 이는 subject가 observer의 구체적인 유형을 알지 못한 채 observer가 event에 반응하는 방식을 보여 주기에 적합합니다.
코드를 세 개의 파일로 구성합니다.
observer.go: observer interface와 구체적인 observer 유형을 Define합니다.주가가 변할 때 주식 정보를 받는
Observerinterface와Update(stockName string, price float64)method를 Create합니다.두 가지 observer 유형을 Implement합니다.
PriceAlert는Thresholdfield(float64)을 가지며, 업데이트될 때 price가 threshold보다 높으면ALERT: [stockName] at $[price] crossed threshold $[threshold]를, 낮으면WATCHING: [stockName] at $[price] (threshold: $[threshold])를 반환합니다.PriceLogger는LogNamefield(string)을 가지며, 업데이트될 때[logName] recorded [stockName]: $[price]를 반환합니다.
두 observer 모두 형식이 지정된 응답 문자열을 반환하는 method를 가져야 합니다.
stock.go: observer를 관리하고 주가 변경을 알리는 subject를 Create합니다.Name(string),Price(float64), observer slice를 field로 갖는Stockstruct를 만듭니다. 다음 method를 Add합니다.Subscribe(o Observer)를 사용하여 observer를 Add합니다.SetPrice(price float64) []string는 price를 업데이트하고 모든 observer에게 알리며, 모든 응답의 slice를 반환합니다.
또한
NewStock(name string, initialPrice float64) *Stockconstructor를 Create합니다.main.go: observer가 연결된 stock을 설정하고 주가 변경을 시뮬레이션합니다.stock name과 initial price를 읽습니다. 그런 다음 Create할 observer의 수를 읽습니다. 각 observer에 대해 유형(
alert또는logger)과 configuration(alert의 threshold, logger의 log name)을 읽습니다. 각 observer를 stock에 Subscribe합니다.마지막으로 새 price를 읽고 stock을 업데이트한 다음, 각 observer의 응답을 한 줄씩 출력합니다.
다음 입력이 제공됩니다.
- 1번째 줄: Stock name
- 2번째 줄: Initial price
- 3번째 줄: Observer 수
- 각 observer: 유형(
alert또는logger), 그다음 configuration value - 마지막 줄: 설정할 새 price
모든 price를 소수점 이하 두 자리로 Format합니다.
예를 들어 다음이 주어졌다고 합시다.
GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50출력은 다음과 같아야 합니다.
ALERT: GOOG at $160.50 crossed threshold $155.00
TradeLog recorded GOOG: $160.50
ALERT: GOOG at $160.50 crossed threshold $140.00그리고 다음이 주어졌다고 합시다.
AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25출력은 다음과 같아야 합니다.
DailyLog recorded AAPL: $175.25
WATCHING: AAPL at $175.25 (threshold: $200.00)Stock은 alert에 알리는지 logger에 알리는지 알 필요가 없다는 점에 주목하세요. 단지 각 observer에서 Update를 호출합니다. 이러한 결합도 감소 덕분에 Stock 코드를 수정하지 않고도 새로운 observer 유형을 쉽게 추가할 수 있습니다!
직접 해보기
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 주식 이름 읽기
stockName, _ := reader.ReadString('\n')
stockName = strings.TrimSpace(stockName)
// 초기 가격 읽기
initialPriceStr, _ := reader.ReadString('\n')
initialPrice, _ := strconv.ParseFloat(strings.TrimSpace(initialPriceStr), 64)
// 옵저버 수 읽기
numObserversStr, _ := reader.ReadString('\n')
numObservers, _ := strconv.Atoi(strings.TrimSpace(numObserversStr))
// TODO: NewStock 생성자를 사용하여 새 Stock 생성
// TODO: 각 옵저버를 순회
for i := 0; i < numObservers; i++ {
// 옵저버 유형 읽기 ("alert" 또는 "logger")
observerType, _ := reader.ReadString('\n')
observerType = strings.TrimSpace(observerType)
// 구성 값 읽기
configValue, _ := reader.ReadString('\n')
configValue = strings.TrimSpace(configValue)
// TODO: 유형에 따라 적절한 옵저버 생성
// - "alert"의 경우: configValue를 float64 임계값으로 파싱하여 PriceAlert 생성
// - "logger"의 경우: configValue를 LogName으로 사용하여 PriceLogger 생성
// TODO: 옵저버를 stock에 구독
}
// 새 가격 읽기
newPriceStr, _ := reader.ReadString('\n')
newPrice, _ := strconv.ParseFloat(strings.TrimSpace(newPriceStr), 64)
// TODO: 새 가격을 설정하고 응답을 가져오세요
// TODO: 각 응답을 한 줄씩 출력하세요
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서직접 연습해 보세요: 온라인 Go 컴파일러