Wednesday, January 2, 2008

Singleton Design Pattern

For this example, we will design an application to remotely monitor the temperature and memory usage of the PC. Each monitoring function runs in a separate thread and monitors corresponding elements to produce a value every 1 second. This information needs to be relayed across to the remote device connected via the Serial communication interface of the PC.
Well with requirements as simple as this, let’s dig in

TemperatureMonitor Class Listing

Public Class TemperatureMonitor

  Private _IsExitSignalled As Boolean

 

  Public Sub New()

    _IsExitSignalled = False

  End Sub

 

  Public Sub StartMonitoring()

    Dim oSerialCommunicator As SerialCommunicator = New SerialCommunicator

    Dim th As System.Threading.Thread = New System.Threading.Thread(AddressOf _MonitoringThread)

    th.Start(oSerialCommunicator)

  End Sub

 

  Private Sub _MonitoringThread(ByVal oSerialCommunicator As Object)

    Dim oSC As SerialCommunicator = CType(oSerialCommunicator, SerialCommunicator)

    Do

      'Add steps here to perform actual temperatur monitoring,

      oSC.SendMonitoredInfo("Memory Temperature Message")

      Threading.Thread.Sleep(2000) 'Sleep for 2 seconds

    Loop Until _IsExitSignalled

  End Sub

 

  Public Sub SignalExit()

    _IsExitSignalled = True

  End Sub

End Class



MemoryMonitor Class Listing

Public Class MemoryMonitor

  Private _IsExitSignalled As Boolean

 

  Public Sub New()

    _IsExitSignalled = False

  End Sub

 

  Public Sub StartMonitoring()

    Dim oSerialCommunicator As SerialCommunicator = New SerialCommunicator

    Dim th As System.Threading.Thread = New System.Threading.Thread(AddressOf _MonitoringThread)

    th.Start(oSerialCommunicator)

  End Sub

 

  Private Sub _MonitoringThread(ByVal oSerialCommunicator As Object)

    Dim oSC As SerialCommunicator = CType(oSerialCommunicator, SerialCommunicator)

    Do

      'Add steps here to perform actual memory monitoring,

      oSC.SendMonitoredInfo("Memory Monitor Message")

      Threading.Thread.Sleep(2000) 'Sleep for 2 seconds

    Loop Until _IsExitSignalled

  End Sub

 

  Public Sub SignalExit()

    _IsExitSignalled = True

  End Sub

End Class



SerialCommunicator Class Listing

Public Class SerialCommunicator

 

  Public Sub New()

    'Open the communication port

    Console.WriteLine("Opening Serial Port")

  End Sub

 

  Public Sub SendMonitoredInfo(ByVal Message As String)

    Console.WriteLine("Sending message " & Message)

  End Sub

 

End Class



And here is what our classes look like



So what else, well the test harness to test our little application

modMain Module Listing

Module modMain

  Sub Main()

    Dim oTemperatureMonitor As TemperatureMonitor = New TemperatureMonitor

    Dim oMemoryMonitor As MemoryMonitor = New MemoryMonitor

 

    oTemperatureMonitor.StartMonitoring()

    oMemoryMonitor.StartMonitoring()

 

    Console.WriteLine("Press Enter to stop monitoring")

    Console.ReadLine()

 

    oTemperatureMonitor.SignalExit()

    oMemoryMonitor.SignalExit()

 

    Console.WriteLine("Monitoring stopped")

    Console.ReadLine()

  End Sub

End Module



And a first look at the results



All this looks pretty simple on the face of it; however, as any experienced programmer who has used the COM port of a PC will tell you, you cannot concurrently open the same port more than once. This implies that the information that the Temperature Monitoring thread will produce will need to be sent over the serial interface in a timeshared fashion along with the information produced by the Memory Monitoring thread. This also implies that the highlighted portion in the output which indicates that the port has been opened concurrently is an issue and this application cannot be used for sending messages out.
So, how do we fix this port sharing issue? Put the Singleton pattern to use. We transform our SerialCommunicator class into a singleton so that it returns a single instance of itself to all requesting methods. With only a single instance of the SerialCommunicator, we eliminate the issue of opening the serial port concurrently. To transform this class into a Singleton, the first thing we need to do is to prevent its external instantiation using the New keyword. We can achieve this by changing the scope of the constructor to Private.
Hold on, won’t that prevent object instantiation? In the conventional sense, Yes! However a private constructor implies that the object can be instantiated within the class. What good is that? How will our TemperatureMonitor and MemoryMonitor acquire this instance? Ok, Enough beating around the bush. Just add a shared method that creates and returns an instance of the class. Ensure that it creates an instance only if an instance of the class does not exist. If an instance exists, it simply returns that instance.
Well, here is the new and improved Singleton SerialCommunicator class –

SerialCommunicator Class Listing

Public Class SerialCommunicator

 

  Private Shared _oSerialCommunicator As SerialCommunicator

 

  Private Sub New()

    'Open the communication port

    Console.WriteLine("Opening Serial Port")

  End Sub

 

  Public Shared Function GetSerialCommunicator() As SerialCommunicator

    If _oSerialCommunicator Is Nothing Then

      _oSerialCommunicator = New SerialCommunicator

    End If

    Return _oSerialCommunicator

  End Function

 

  Public Sub SendMonitoredInfo(ByVal Message As String)

    SyncLock Me

      Console.WriteLine("Sending message " & Message)

    End SyncLock

  End Sub

 

End Class



We need to change our TemperatureMonitor and MemoryMonitor classes as well since the public constructor of the SerialCommunicator is no longer available.

TemperatureMonitor StartMonitoring Method Listing

Public Class TemperatureMonitor

  'No change in code here

 

  Public Sub StartMonitoring()

    Dim oSerialCommunicator As SerialCommunicator = SerialCommunicator.GetSerialCommunicator

    Dim th As System.Threading.Thread = New System.Threading.Thread(AddressOf _MonitoringThread)

    th.Start(oSerialCommunicator)

  End Sub

 

  'No change in code or here

End Class



MemoryMonitor StartMonitoring Method Listing

Public Class MemoryMonitor

  'No change in code here

 

  Public Sub StartMonitoring()

    Dim oSerialCommunicator As SerialCommunicator = SerialCommunicator.GetSerialCommunicator

    Dim th As System.Threading.Thread = New System.Threading.Thread(AddressOf _MonitoringThread)

    th.Start(oSerialCommunicator)

  End Sub

 

  'No change in code here

End Class



Our class diagram is identical to our original. The only difference being the scope of the SerialCommunicator constructor and an additional method in the same class to create and return an instance of the SerialCommunicator class.



With no change in our test harness, let’s put our application to the test



Notice that we only have one “Opening Serial Port” message indicating our Singleton pattern is in effect.

No comments: