こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるChain of Responsibilityパターンについて紹介します。
個人的には、実用的というよりは頭の体操に近い内容になっていると思いますが、知っておいて損はないパターンだと思います。
<目次>
前回
デザインパターンまとめ
Chain of Responsibilityパターン
chain は「鎖」、responsibility は「責任」を意味します。故に、Chain of Responsibility パターンとは、「責任」を負ったものが、「鎖」状につながれた状態をイメージさせるパターンです。
前回は、インスタンスのメソッドを繋げてこのパターンを実装しました。
今回は、syncパッケージのMapを使って、Broker Chainという形式で実装したいと思います。詳しい説明は、サンプル(ロガー)を実装しつつ行いたいと思います!
main関数
以下の状態からプログラムを実装していきます。
1 2 3 4 5 |
package main func main() { } |
定数の定義
ロガーのログレベルを定数で宣言します。これは、プログラムを見やすくするためです。
1 2 3 4 5 6 7 |
const ( DEBUG = "DEBUG" INFO = "INFO" WARNING = "WARNING" ERROR = "ERROR" CRITICAL = "CRITICAL" ) |
ログの保存場所
ログの保存場所をグローバルで宣言します。
1 |
var loggerStrings []string |
クエリ構造体の実装
クエリ構造体を実装します。
1 2 3 4 |
type Query struct { LoggerLevel string Message string } |
Observerを定義
以下のインターフェースを定義します。
1 2 3 4 5 6 7 8 9 |
type Observer interface { Handle(q *Query) } type Observable interface { Subscribe(o Observer) UnSubscribe(o Observer) Fire(q *Query) } |
Observerは、観察者という意味です。これは、ログそのものだと考えてください。
本実装の最終段階では、生成するログ自体をインスタンス化します。その一つ一つをObserverとして扱います。このObserverには、Handleという一つのメソッドを持っており、ここに個別に実装したい処理を書きます。
Observableは、Observerが実行できる処理を実装します。Subscribe(ログを保存する)、UnSubscribe(ログを削除する)、Fire(ログを処理する)の3つの機能を持たせました。
Platformを実装
Observerを管理する構造体を実装します。
1 2 3 |
type Platform struct { observers sync.Map } |
Platformメソッド(s)
Platformのメソッドを実装します。
Subscribe
KeyにObserver、Valueにログレベルを指定して、sync.Map保存します。
1 2 3 |
func (p *Platform) Subscribe(o Observer, loggerLevel string) { p.observers.Store(o, loggerLevel) } |
UnSubscribe
KeyにObserverを指定して、sync.Mapからデータを削除します。
1 2 3 |
func (p *Platform) UnSubscribe(o Observer) { p.observers.Delete(o) } |
Fire
sync.Mapに格納されているデータをループして、目的のログを取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
func (p *Platform) Fire(q *Query) { // sync.Mapの全ての値を取り出すときの引数 // func (*sync.Map).Range(f func(key interface{}, value interface{}) bool) p.observers.Range(func(key, value interface{}) bool { if key == nil { return false } if q.LoggerLevel == value.(string) { key.(Observer).Handle(q) } return true }) } |
引数には、Queryを渡しています。これは、ログを呼び出すときに、ロガーレベルを調整する役割を担っています。
また、sync.Mapにはログのインスタンスそのものが格納されているので「key.(Observer).Handle(q)」の様にメソッドを呼び出すことができます。ここが本記事のポイントですね。
ロガーの定義
ロガーを定義します。
1 2 3 4 |
type Logger struct { platform *Platform prefix string } |
ロガーのコンストラクタ
ロガーのコンストラクタを定義します。
1 2 3 4 5 6 |
func NewLogger(platform *Platform, name string) *Logger { return &Logger{ platform: platform, prefix: name, } } |
クエリメソッドを定義
ログをセットするクエリメソッドを定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func (l *Logger) DEBUG() { q := Query{LoggerLevel: DEBUG} l.platform.Fire(&q) } func (l *Logger) INFO() { q := Query{LoggerLevel: INFO} l.platform.Fire(&q) } func (l *Logger) WARNING() { q := Query{LoggerLevel: WARNING} l.platform.Fire(&q) } func (l *Logger) ERROR() { q := Query{LoggerLevel: ERROR} l.platform.Fire(&q) } func (l *Logger) CRITICAL() { q := Query{LoggerLevel: CRITICAL} l.platform.Fire(&q) } |
ログ表示メソッド
ログを表示するメソッドを定義します。
1 2 3 4 5 6 7 8 9 10 11 |
func (l *Logger) String() { l.DEBUG() l.INFO() l.WARNING() l.ERROR() l.CRITICAL() for _, log := range loggerStrings { fmt.Println(log) } loggerStrings = make([]string, 0) } |
「l.DEBUG()」などで、表示するログレベルを調整します。
※このロガーはあくまでサンプルなので現場ではこんな実装はしません
Platformとロガーを繋ぐ構造体
Platformとロガーを繋ぐ構造体を定義します。
1 2 3 4 |
type PlagformLoggerModifer struct { platform *Platform logger *Logger } |
ロガー変更構造体
ロガー変更構造体を定義します。
1 2 3 4 5 6 7 8 9 10 11 |
type LoggerModifier struct { PlagformLoggerModifer loggerLevel string message string } func NewLoggerModifier(p *Platform, l *Logger, loggerLevel, msg string) *LoggerModifier { d := &LoggerModifier{PlagformLoggerModifer{p, l}, loggerLevel, msg} p.Subscribe(d, loggerLevel) return d } |
特別なログを表示したい場合は、以下のように新しい構造体を作成します。
1 2 3 4 5 |
// 例 type LastLoggerModifier struct {...} func NewLastLoggerModifier(p *Platform, l *Logger, loggerLevel, msg string) *LoggerModifier { ... } |
ロガー変更構造体のメソッド
ロガー変更構造体には、独自の処理を実装します。
1 2 3 4 5 6 7 8 9 10 11 |
func (l *LoggerModifier) Handle(q *Query) { loggerStrings = append(loggerStrings, fmt.Sprintf( "%s -> %s: %s", l.logger.prefix, q.LoggerLevel, l.message)) } func (l *LoggerModifier) Reflsh() { l.platform.UnSubscribe(l) } |
Handleメソッドにクエリからメッセージを取得し、グローバル変数に保存する処理を、Reflshメソッドにsync.Mapからログを削除する処理をそれぞれ実装しました。
使ってみよう
ここまで実装したソースコードを使ってみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
func main() { apiPlatforrm := &Platform{sync.Map{}} logger := NewLogger(apiPlatforrm, "Logger") debug := NewLoggerModifier(apiPlatforrm, logger, DEBUG, "This is a debug log") info := NewLoggerModifier(apiPlatforrm, logger, INFO, "This is an info log") warn := NewLoggerModifier(apiPlatforrm, logger, WARNING, "This is a warning log") err := NewLoggerModifier(apiPlatforrm, logger, ERROR, "This is an error log") cri := NewLoggerModifier(apiPlatforrm, logger, CRITICAL, "This is a critical log") // ログを出力 logger.String() // ログを消す debug.Reflsh() info.Reflsh() warn.Reflsh() err.Reflsh() cri.Reflsh() fmt.Println("====================================") // ログを出力 logger.String() } |
1 2 3 4 5 6 7 |
$ go run main.go Logger -> DEBUG: This is a debug log Logger -> INFO: This is an info log Logger -> WARNING: This is a warning log Logger -> ERROR: This is an error log Logger -> CRITICAL: This is a critical log ==================================== |
OKですね。logger.String()を呼び出すだけで、全てのログが出力されました。
一応、動きましたが、結構使いづらいソースコードになりましたねw
本番で実装したらチームメンバーに怒られるかもしれません^^
まとめ
今回のプログラムはちゃんと実装できているのか一抹の不安があるので、参考程度にしておいてください。。
やりたかったことは、sync.Mapのようなキャッシュにインスタンスを保存して、後から一度に実行(数珠繋ぎ)!みたいなことでしたが、改良が必要ですね^^:
でも考え方だけは、参考になると思います(ポジティブ)!
それでは、また!
Go言語まとめ
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
package main import ( "fmt" "sync" ) const ( DEBUG = "DEBUG" INFO = "INFO" WARNING = "WARNING" ERROR = "ERROR" CRITICAL = "CRITICAL" ) var loggerStrings []string type Query struct { LoggerLevel string Message string } type Observer interface { Handle(q *Query) } type Observable interface { Subscribe(o Observer) UnSubscribe(o Observer) Fire(q *Query) } type Platform struct { observers sync.Map } func (p *Platform) Subscribe(o Observer, loggerLevel string) { p.observers.Store(o, loggerLevel) } func (p *Platform) UnSubscribe(o Observer) { p.observers.Delete(o) } func (p *Platform) Fire(q *Query) { // sync.Mapの全ての値を取り出すときの引数 // func (*sync.Map).Range(f func(key interface{}, value interface{}) bool) p.observers.Range(func(key, value interface{}) bool { if key == nil { return false } if q.LoggerLevel == value.(string) { key.(Observer).Handle(q) } return true }) } type Logger struct { platform *Platform prefix string } func NewLogger(platform *Platform, name string) *Logger { return &Logger{ platform: platform, prefix: name, } } func (l *Logger) DEBUG() { q := Query{LoggerLevel: DEBUG} l.platform.Fire(&q) } func (l *Logger) INFO() { q := Query{LoggerLevel: INFO} l.platform.Fire(&q) } func (l *Logger) WARNING() { q := Query{LoggerLevel: WARNING} l.platform.Fire(&q) } func (l *Logger) ERROR() { q := Query{LoggerLevel: ERROR} l.platform.Fire(&q) } func (l *Logger) CRITICAL() { q := Query{LoggerLevel: CRITICAL} l.platform.Fire(&q) } func (l *Logger) String() { l.DEBUG() l.INFO() l.WARNING() l.ERROR() l.CRITICAL() for _, log := range loggerStrings { fmt.Println(log) } loggerStrings = make([]string, 0) } type PlagformLoggerModifer struct { platform *Platform logger *Logger } // func (l *PlagformLoggerModifer) Handle(*Query) { // nothing // } type LoggerModifier struct { PlagformLoggerModifer loggerLevel string message string } func NewLoggerModifier(p *Platform, l *Logger, loggerLevel, msg string) *LoggerModifier { d := &LoggerModifier{PlagformLoggerModifer{p, l}, loggerLevel, msg} p.Subscribe(d, loggerLevel) return d } func (l *LoggerModifier) Handle(q *Query) { loggerStrings = append(loggerStrings, fmt.Sprintf( "%s -> %s: %s", l.logger.prefix, q.LoggerLevel, l.message)) } func (l *LoggerModifier) Reflsh() { l.platform.UnSubscribe(l) } func main() { apiPlatforrm := &Platform{sync.Map{}} logger := NewLogger(apiPlatforrm, "Logger") debug := NewLoggerModifier(apiPlatforrm, logger, DEBUG, "This is a debug log") info := NewLoggerModifier(apiPlatforrm, logger, INFO, "This is an info log") warn := NewLoggerModifier(apiPlatforrm, logger, WARNING, "This is a warning log") err := NewLoggerModifier(apiPlatforrm, logger, ERROR, "This is an error log") cri := NewLoggerModifier(apiPlatforrm, logger, CRITICAL, "This is a critical log") // ログを出力 logger.String() // ログを消す debug.Reflsh() info.Reflsh() warn.Reflsh() err.Reflsh() cri.Reflsh() fmt.Println("====================================") // ログを出力 logger.String() } |
コメントを残す
コメントを投稿するにはログインしてください。