Skip to main content
  1. internet/

设计模式之观察者模式

·1401 words·3 mins·

What #

观察者模式是一种设计模式,通常用于解耦观察者与被观察者。

观察者模式中,被观察者称为主题。主题与观察者通常是1对多的关系。

观察者需要获取主题的变化。在观察者模式中,往往采用主题向观察者push的方式来传递数据。

Why #

在未考虑设计模式/原则的代码中,实现上述功能可以简述为:

package main

type Observer1 struct{}

func (n Observer1) Update(msg string) {
	// do something
}

type Observer2 struct{}

func (n Observer2) Update(msg string) {
	// do something
}

type Observer3 struct{}

func (n Observer3) Update(msg string) {
	// do something
}

type Topic struct {
	msg string
}

func (t *Topic) Notify() {
	Observer1{}.Update(t.msg)
	Observer2{}.Update(t.msg)
	Observer3{}.Update(t.msg)
}

我们创建了三个观察者,当Topic需要向观察者发送数据时,需要实例化这三个观察者。

如果我们需要再新增一个观察者,那么Topic的Notify方法中需要再实例化这个新的观察者。

Topic和观察者耦合在了一起。

How #

通过将观察者抽象为接口,可以实现Topic和观察者之间的解耦。

package main

type Observer interface {
	Update(msg string)
}

type Observer1 struct{}

func (n Observer1) Update(msg string) {
	// do something
}

type Observer2 struct{}

func (n Observer2) Update(msg string) {
	// do something
}

type Observer3 struct{}

func (n Observer3) Update(msg string) {
	// do something
}

type Topic struct {
	msg string
	observers []Observer
}

func (t *Topic) Notify() {
	for _, observer := range t.observers {
		observer.Update(t.msg)
	}
}

可以看到,Topic中的Notify方法没有再实例化观察者,而是遍历当前已注册的观察者。

当我们需要新增观察者的时候,通过Topic的注册接口添加到observers即可。注销同理。

Example #

理论总是很简单,实践往往复杂很多。

antlr中的错误监听 #

antlr是一个开源的语法解析工具。在语法解析过程中,有可能解析失败,这时候需要处理错误。

antlr作为一个开源工具,不可能为用户提供各种错误处理方式,而通过观察者模式,antlr将错误处理的能力移交给了使用者。

定义观察者行为 #

antlr中定义了观察者的行为:

type ErrorListener interface {
	SyntaxError(recognizer Recognizer, offendingSymbol interface{}, line, column int, msg string, e RecognitionException)
	ReportAmbiguity(recognizer Parser, dfa *DFA, startIndex, stopIndex int, exact bool, ambigAlts *BitSet, configs ATNConfigSet)
	ReportAttemptingFullContext(recognizer Parser, dfa *DFA, startIndex, stopIndex int, conflictingAlts *BitSet, configs ATNConfigSet)
	ReportContextSensitivity(recognizer Parser, dfa *DFA, startIndex, stopIndex, prediction int, configs ATNConfigSet)
}

并且实现了两个观察者:

  • 默认的观察者(不处理错误的观察者)。
  • 将语法错误输出到终端的观察者。
type DefaultErrorListener struct {
}

func NewDefaultErrorListener() *DefaultErrorListener {
	return new(DefaultErrorListener)
}

func (d *DefaultErrorListener) SyntaxError(recognizer Recognizer, offendingSymbol interface{}, line, column int, msg string, e RecognitionException) {
}

func (d *DefaultErrorListener) ReportAmbiguity(recognizer Parser, dfa *DFA, startIndex, stopIndex int, exact bool, ambigAlts *BitSet, configs ATNConfigSet) {
}

func (d *DefaultErrorListener) ReportAttemptingFullContext(recognizer Parser, dfa *DFA, startIndex, stopIndex int, conflictingAlts *BitSet, configs ATNConfigSet) {
}

func (d *DefaultErrorListener) ReportContextSensitivity(recognizer Parser, dfa *DFA, startIndex, stopIndex, prediction int, configs ATNConfigSet) {
}

type ConsoleErrorListener struct {
	*DefaultErrorListener
}

func NewConsoleErrorListener() *ConsoleErrorListener {
	return new(ConsoleErrorListener)
}

func (c *ConsoleErrorListener) SyntaxError(recognizer Recognizer, offendingSymbol interface{}, line, column int, msg string, e RecognitionException) {
	fmt.Fprintln(os.Stderr, "line "+strconv.Itoa(line)+":"+strconv.Itoa(column)+" "+msg)
}

自定义观察者 #

作为antlr的用户,我需要自己捕获语法错误,因此,我实现了自己的错误监听器:

type ErrListener struct {
	antlr.DefaultErrorListener

	errList []string
}

func (el *ErrListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int,
	msg string, e antlr.RecognitionException) {
	el.errList = append(el.errList, fmt.Sprintf("pos: %d:%d, msg: %s", line, column, msg))
}

antlr的语法解析器提供了注册错误监听器的方法,所以在初始化解析器的时候将ErrListener注册进去即可:

// parser中有一个观察者数组:listeners []ErrorListener
parser := NewParser()
parser.AddErrorListener(&ErrListener{})

在实际解析到错误时,antlr会创建一个代理来将错误信息分发给观察者。实现如下:

func (p *BaseParser) NotifyErrorListeners(msg string, offendingToken Token, err RecognitionException) {
	p._SyntaxErrors++
	line := offendingToken.GetLine()
	column := offendingToken.GetColumn()
	listener := p.GetErrorListenerDispatch()
	listener.SyntaxError(p, offendingToken, line, column, msg, err)
}

func (b *BaseRecognizer) GetErrorListenerDispatch() ErrorListener {
	return NewProxyErrorListener(b.listeners)
}


type ProxyErrorListener struct {
	*DefaultErrorListener
	delegates []ErrorListener
}

func NewProxyErrorListener(delegates []ErrorListener) *ProxyErrorListener {
	if delegates == nil {
		panic("delegates is not provided")
	}
	l := new(ProxyErrorListener)
	l.delegates = delegates
	return l
}

func (p *ProxyErrorListener) SyntaxError(recognizer Recognizer, offendingSymbol interface{}, line, column int, msg string, e RecognitionException) {
	for _, d := range p.delegates {
		d.SyntaxError(recognizer, offendingSymbol, line, column, msg, e)
	}
}

func (p *ProxyErrorListener) ReportAmbiguity(recognizer Parser, dfa *DFA, startIndex, stopIndex int, exact bool, ambigAlts *BitSet, configs ATNConfigSet) {
	for _, d := range p.delegates {
		d.ReportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs)
	}
}

func (p *ProxyErrorListener) ReportAttemptingFullContext(recognizer Parser, dfa *DFA, startIndex, stopIndex int, conflictingAlts *BitSet, configs ATNConfigSet) {
	for _, d := range p.delegates {
		d.ReportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs)
	}
}

func (p *ProxyErrorListener) ReportContextSensitivity(recognizer Parser, dfa *DFA, startIndex, stopIndex, prediction int, configs ATNConfigSet) {
	for _, d := range p.delegates {
		d.ReportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs)
	}
}

小结 #

从antlr处理错误的代码中,我们可以学到:

  1. 观察者模式提供了一种能力,让用户参与到行为(主题)的捕获与处理。
  2. 通过工厂的方式来创建了一个代理来向观察者发送数据,进一步解耦了实体(解析器)与观察者。
  3. 可以通过定义一个无作为的观察者(DefaultErrorListener),方便用户实现观察者。