はじめに
参考文献に記載した書籍を読んでObserverパターンを勉強しました。
その時の備忘録として、自分なりにObserverパターンを説明してみます。
Observerパターンとは?
一言で言うと、何か状態が変化した時に、みんなに知らせるデザインパターンです。
例えば、空港であなたは荷物検査を担当しているとします。
何か異常なものを発見した時に、あなたは警察、警備員、スワットなど各チームへ連絡する必要があります。
連絡をするために所定のボタンを押すと、ボタンの状態が正常から異常に変わります。
状態が変わったことで、警察、警備員、スワットに連絡が行きます。
Observerパターンでは
ボタンはSubject、警察、警備員、スワットはObserverとなります。
つまりボタン(=Subject)の状態が変化すると、警察、警備員、スワット(=Observer)に通知が行きます。
この例をあとで実装していきます。
※ 今は雰囲気がわかれば問題ないです。
また、通知の方法は主に2通りあります。
PUSH型とPULL型です。今回の実装はPUSH型で実装しています。
PUSH型
Updateの引数に変更された状態を渡すのがPUSH型です。
私はまだデメリットの実感はないですが、引数を指定しているため限定されるというデメリットがあるそうです。
ただ、簡単な処理や、処理が限定されているときはシンプルに書くことが出来ます。
PULL型
ObjectがSubjectに状態変化を問い合わせに行くのがPULL型です。
こちらも実感してませんが、処理が複雑になるのがデメリットだそうです。
※ 実感を持つのに良い実装例があれば教えてください。
ここからはダメな例とObserverパターンを使った良い例を比較してみていきます。
ダメな例
簡単な例として、荷物検査を3回して7が出たら異常ありと言うことにします。
そして異常があった場合に、警察とスワットに連絡します。
func main() {
swatObserver := &SwatObserver{}
policeObserver := &PoliceObserver{}
for i := 0; i < 3; i++ {
randNum := rand.Intn(10)
if randNum == 7 {
swatObserver.Update("異常を検知")
policeObserver.Update("異常を検知")
// 警備員を追加
} else {
fmt.Println("荷物No.:", randNum, " は問題なし.")
}
time.Sleep(1 * time.Second)
}
}
type SwatObserver struct {
str string
}
func (s *SwatObserver) Update(str string) {
swat := SwatObserver{str: str}
swat.Send()
}
func (s *SwatObserver) Send() {
fmt.Println("swat : ", s.str)
}
type PoliceObserver struct {
str string
}
func (p *PoliceObserver) Update(str string) {
police := PoliceObserver{str: str}
police.Send()
}
func (p *PoliceObserver) Send() {
fmt.Println("police : ", p.str)
}
// swat : 異常を検知
// police : 異常を検知
// 荷物No.: 9 は問題なし.
// 荷物No.: 1 は問題なし.
説明
警備員を書き忘れ他ので追加する場合を考えてみます。
まずは、警備員の構造体にUpdate,Sendを実装します。
次に、main関数の処理を修正します。
newしてsecurityGuardObserver.Update("異常を検知")
を追加します。
このコードにどのようなデメリットがあるでしょうか?
- observerの追加や削除ができない。コメントアウトで削除は出来るが。。
- インタフェースでなく具象実装に対して実装している
- 変更の範囲が大きい
- etc...
Observerパターンの活用した良い例
ではObserverパターンを活用した実装をみていきます。
図解
登場人物を紹介します。
・Subject
Subjectのインタフェースを定義しています。
Observerの追加や削除、そして状態の変化が会った時に、Observerのインタフェースに通知する役割を持っています。
これがボタンの役割を担います、
・errorSubject
Subjectインタフェースの実装です。
・Observer
Observerのインタフェースを定義しています。
SubjectからUpdateメソッドが呼ばれます。Updateメソッドを実装している◯◯Observerに通知がいきます。
・PoliceObserver/SwatObserver
Observerインタフェースの実装です。
警察官とスワットの役割を担います。
ソースコード
では上記で示した図をそのままコードに落としてみます。
・Subject
type Subject interface {
RegisterObserver(observer observer.Observer)
RemoveObserver(observer observer.Observer)
NotifyObserver()
DetectError(str string)
}
・errorSubject
type ErrorSubject struct {
observers []observer.Observer
str string
}
func (e *ErrorSubject) RegisterObserver(observer observer.Observer) {
e.observers = append(e.observers, observer)
}
func (e *ErrorSubject) RemoveObserver(observer observer.Observer) {
result := ErrorSubject{}
for _, o := range e.observers {
if o != observer {
result.observers = append(result.observers, o)
}
}
e.observers = result.observers
}
func (e *ErrorSubject) NotifyObserver() {
for _, o := range e.observers {
o.Update(e.str)
}
}
func (e *ErrorSubject) DetectError(str string) {
e.str = str
e.NotifyObserver()
}
・Observer
type Observer interface {
Update(str string)
Send()
}
・PoliceObserver/SwatObserver
type PoliceObserver struct {
str string
}
func (p *PoliceObserver) Update(str string) {
police := PoliceObserver{str:str}
police.Send()
}
func (p *PoliceObserver) Send() {
fmt.Println("police : ", p.str)
}
type SwatObserver struct {
str string
}
func (s *SwatObserver) Update(str string) {
swat := SwatObserver{str:str}
swat.Send()
}
func (s *SwatObserver) Send() {
fmt.Println("swat : ", s.str)
}
説明
ダメな例で上がった以下の問題が解決されました。
-
observerの追加や削除ができない。コメントアウトで削除は出来るが。。
→ RegisterObserver/RemoveObserverによって追加削除が出来る -
インタフェースでなく具象実装に対して実装している
→ subject/observerインタフェースに対して実装している。 -
変更の範囲が大きい
→ 以下の図のように追加する場合はUpdateとSaveを実装した構造体を定義するだけです。 -
etc...
最後に
今の所自分がしてきた実装にObserverパターンの採用はできていないので、
使えそうなチャンスがあれば採用してみたいです。
参考文献
以下の書籍を参考にしています。
- アジャイルソフトウェア開発の奥義
- Java言語で学ぶデザインパターン入門
- Head Firstデザインパターン