Menu
Coddy logo textTech

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 は控えめに使用してください。便利ではありますが、テストを難しくし、依存関係を隠してしまう可能性のあるグローバル状態を導入します。あなたのユースケースでは、依存性注入のほうが適切な代替手段にならないか検討してください。

challenge icon

チャレンジ

簡単

アプリケーション全体でロガーのインスタンスが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"
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Goオンラインコンパイラ