Singleton (シングルトン)
一人っ子、単一のこと・もの
Singletonパターンは、インスタンスがひとつしかない唯一のオブジェクトを生成し、そのインスタンスを取得する方法を公開します。
例えば、アプリケーション実行時のログを出力するような場合を考えます。ログのファイル名をシステム日付+時刻でつけるとしましょう。ログ出力を初回に行う際に、ログファイル名を決定し、以後はそのログファイルにログを出力します。
この場合、ログファイル名はアプリケーション実行時に1つだけでなくてはなりません。このような場合、Singletonが使えます。
以下に、コード例を示します。
C#
/// <summary>
/// ログ出力クラス
/// </summary>
public class SingletonLogger
{
/// <summary>
/// ログ出力パス
/// </summary>
private string _logPath;
/// <summary>
/// プロパティ ログ出力パス
/// </summary>
public string LogPath
{
get
{
return _logPath;
}
}
/// <summary>
/// コンストラクタ
/// </summary>
/// <remarks>new でインスタンス化できないよう、privateでコンストラクタを定義</remarks>
private SingletonLogger()
{
_logPath = DateTime.Now.ToString("yyyyMMddhhmmssfff") + ".log";
}
/// <summary>
/// 自身の唯一のインスタンス
/// </summary>
private static SingletonLogger _uniqueLogger;
/// <summary>
/// プロパティ インスタンス
/// </summary>
/// <remarks>自身の唯一のインスタンスを返す</remarks>
public static SingletonLogger Instance
{
get
{
if ( _uniqueLogger == null )
{
_uniqueLogger = new SingletonLogger();
}
return _uniqueLogger;
}
}
/// <summary>
/// ログ出力
/// </summary>
/// <param name="message"></param>
public void WriteLog(string message)
{
// ログ出力処理
// ・・・
}
}
VB
''' <summary>
''' ログ出力クラス
''' </summary>
''' <remarks></remarks>
Public Class SingletonLogger
''' <summary>
''' ログ出力パス
''' </summary>
''' <remarks></remarks>
Private _logPath As String
''' <summary>
''' プロパティ ログ出力パス
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property LogPath() As String
Get
Return _logPath
End Get
End Property
''' <summary>
''' コンストラクタ
''' </summary>
''' <remarks>new でインスタンス化できないよう、privateでコンストラクタを定義</remarks>
Private Sub New()
_logPath = DateTime.Now.ToString("yyyyMMddhhmmssfff") & ".log"
End Sub
''' <summary>
''' 自身の唯一のインスタンス
''' </summary>
''' <remarks></remarks>
Private Shared _uniqueLogger As SingletonLogger
''' <summary>
''' プロパティ インスタンス
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks>自身の唯一のインスタンスを返す</remarks>
Public Shared ReadOnly Property Instance() As SingletonLogger
Get
If _uniqueLogger Is Nothing Then
_uniqueLogger = New SingletonLogger
End If
Return _uniqueLogger
End Get
End Property
''' <summary>
''' ログ出力
''' </summary>
''' <param name="message"></param>
Public Sub WriteLog(ByVal message As String)
' ログ出力処理
' ・・・
End Sub
End Class
このコードのポイントは以下の3点です。
- コンストラクタをprivateとして、外からはインスタンス化できないようにする。
- privateな静的フィールドとして、自身の型のフィールドを用意し、クライアントにはそれを提供するようにする。
- Instanceプロパティ内で、上記静的フィールドがnullの場合のみ、新たにインスタンス化するようにし、唯一のオブジェクトを保障する。
以上のコードを使って見ましょう。
C#
public class Program
{
static void Main(string[] args)
{
{
var logger = SingletonLogger.Instance;
Console.WriteLine(logger.LogPath);
}
System.Threading.Thread.Sleep(1000);
{
var logger = SingletonLogger.Instance;
Console.WriteLine(logger.LogPath);
}
Console.ReadKey();
}
}
VB
Public Class Program
Public Shared Sub Main()
With Nothing
Dim logger = SingletonLogger.Instance
Console.WriteLine(logger.LogPath)
End With
System.Threading.Thread.Sleep(1000)
With Nothing
Dim logger = SingletonLogger.Instance
Console.WriteLine(logger.LogPath)
End With
Console.ReadKey()
End Sub
End Class
実行結果
20080608125533068.log
20080608125533068.log
実行結果を見るとわかるように、1つのインスタンスが使いまわされていることがわかります。
さて、以上のコードは、通常の使用方法では問題がないのですが、以下のようにマルチスレッドで動かす場合、1つだけであるはずのインスタンスが、複数生成されてしまうことがあります。
C#
/// ログ出力クラス
/// </summary>
public class SingletonLogger
{
・・・
/// <summary>
/// コンストラクタ
/// </summary>
/// <remarks>new でインスタンス化できないよう、privateでコンストラクタを定義</remarks>
private SingletonLogger()
{
// 重い初期化処理
for ( var i = 0; i < 100000000; i++ )
{
}
_logPath = DateTime.Now.ToString("yyyyMMddhhmmssfff") + ".log";
}
・・・
}
public class Program
{
static void Main(string[] args)
{
SingletonLogger logger1 = null;
var thread1 = new Thread(
() =>
{
logger1 = SingletonLogger.Instance;
}
);
SingletonLogger logger2 = null;
var thread2 = new Thread(
() =>
{
logger2 = SingletonLogger.Instance;
}
);
thread1.Start();
thread2.Start();
while ( logger1 == null || logger2 == null)
{
Thread.Sleep(10);
}
Console.WriteLine(logger1.LogPath);
Console.WriteLine(logger2.LogPath);
Console.ReadKey();
}
}
VB
''' <summary>
''' ログ出力クラス
''' </summary>
''' <remarks></remarks>
Public Class SingletonLogger
・・・
''' <summary>
''' コンストラクタ
''' </summary>
''' <remarks>new でインスタンス化できないよう、privateでコンストラクタを定義</remarks>
Private Sub New()
' 重い初期化処理
For i As Integer = 0 To 100000000
Next i
_logPath = DateTime.Now.ToString("yyyyMMddhhmmssfff") & ".log"
End Sub
・・・
End Class
Public Class Program
Public Shared Sub Main()
Dim loggerHelper1 As New LoggerHelper()
Dim thread1 = New Thread(AddressOf loggerHelper1.SetLogger)
Dim loggerHelper2 As New LoggerHelper()
Dim thread2 = New Thread(AddressOf loggerHelper2.SetLogger)
thread1.Start()
thread2.Start()
While (loggerHelper1.Logger Is Nothing OrElse loggerHelper2.Logger Is Nothing)
Thread.Sleep(10)
End While
Console.WriteLine(loggerHelper1.Logger.LogPath)
Console.WriteLine(loggerHelper2.Logger.LogPath)
Console.ReadKey()
End Sub
Public Class LoggerHelper
Public Logger As SingletonLogger
Public Sub SetLogger()
Me.Logger = SingletonLogger.Instance
End Sub
End Class
End Class
実行結果
20080608011949432.log
20080608011949613.log
ごらんのように、インスタンスが2つ生成された結果、ログ出力パスが変わってしまいます。
この問題を解消する方法はないのでしょうか?
実はあります。このあたり、次回のエントリで取り上げようと思います。
#今回から、.NET Framework 3.5 のコードにしています。