Singleton パターン
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 86/107。
Singletonパターンは、プログラム全体を通して型のインスタンスが1つだけ存在することを保証し、そのインスタンスへのグローバルなアクセスポイントを提供します。これは、設定マネージャー、データベース接続、ロガーなどの共有リソースに役立ちます。
Goでは、スレッドセーフな初期化を保証するために、パッケージレベルの変数とsync.Onceを組み合わせてSingletonを実装します。
package config
import "sync"
type Config struct {
DatabaseURL string
MaxRetries int
}
var (
instance *Config
once sync.Once
)
func GetInstance() *Config {
once.Do(func() {
instance = &Config{
DatabaseURL: "localhost:5432",
MaxRetries: 3,
}
})
return instance
}sync.Onceは、複数のゴルーチンが同時にGetInstance()を呼び出した場合でも、初期化関数が正確に一度だけ実行されることを保証します。その後のすべての呼び出しでは、初期化コードを再実行せずに同じインスタンスが返されます。
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 と cfg2 は同じインスタンスを指していますSingleton は控えめに使用してください。便利ではありますが、テストを難しくし、依存関係を隠してしまう可能性のあるグローバル状態を導入します。あなたのユースケースでは、依存性注入のほうが適切な代替手段にならないか検討してください。
チャレンジ
簡単アプリケーション全体でロガーのインスタンスが1つだけ存在することを保証する、Loggerシングルトンを構築しましょう!これはSingletonパターンの典型的な使用例です。プログラムのすべての部分で、同じロガーの設定と状態を共有したい場合に使います。
コードを2つのファイルに分けて整理します。
logger.go: スレッドセーフなSingletonロガーをImplementします。2つのフィールドを持つ
Logger構造体を作成します。ログメッセージのプレフィックス用のPrefix(string)と、loggedされたメッセージ数を追跡するためのMessageCount(int)です。sync.Onceを使用したpackageレベルの変数で、instanceが1つだけ作成されるようにensureします。GetLogger()関数をImplementし、defaultのプレフィックス"[LOG]"でロガーを初期化して、Singleton instanceを返します。Loggerに次のメソッドを追加します。
SetPrefix(prefix string)- ロガーのプレフィックスをchangesしますLog(message string) string- メッセージ数をincrementsし、formattedされた文字列を返します。[prefix] #[count]: [message]GetCount() int- loggedされたメッセージの合計数を返します
main.go:GetLogger()を複数回呼び出しても同じinstanceが返されることを示します。新しいプレフィックスの値を読み取り、次にメッセージ数と、ログに記録する各メッセージを読み取ります。
ロガーのinstanceを取得し、customプレフィックスを設定してから、各メッセージをログに記録して結果を出力します。すべてのメッセージをログに記録した後、ロガーのinstanceをもう一度取得して(同じものであることを証明するため)、メッセージの合計数を出力します。
次の入力が提供されます。
- 1行目: 設定するcustomプレフィックス
- 2行目: メッセージ数
- 続く行: ログに記録する各メッセージ
たとえば、次の入力の場合:
[APP]
3
Server started
User connected
Request processed出力は次のようになります。
[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3また、次の入力の場合:
[DEBUG]
2
Initializing cache
Cache ready出力は次のようになります。
[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2また、次の入力の場合:
[ERROR]
1
Connection failed出力は次のようになります。
[ERROR] #1: Connection failed
Total messages logged: 1重要なポイントは、GetLogger()を何回呼び出しても、共有されたstateを持つ同じinstanceが必ず取得されることです。存在するLoggerは1つだけなので、メッセージ数は保持されます。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// カスタムプレフィックスを読み取る
prefix, _ := reader.ReadString('\n')
prefix = strings.TrimSpace(prefix)
// メッセージの数を読み取る
countStr, _ := reader.ReadString('\n')
countStr = strings.TrimSpace(countStr)
count, _ := strconv.Atoi(countStr)
// 各メッセージを読み取る
messages := make([]string, count)
for i := 0; i < count; i++ {
msg, _ := reader.ReadString('\n')
messages[i] = strings.TrimSpace(msg)
}
// TODO: GetLogger() を使用してロガーインスタンスを取得する
// TODO: SetPrefix() を使用してカスタムプレフィックスを設定する
// TODO: 各メッセージをログに記録し、結果を出力する
// TODO: ロガーインスタンスを再度取得する(同じものであることを証明するため)
// and print the total message count in format: "Total messages logged: X"
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ