.NET Remoting and Event Handling in VB .NET, Part 3, Page 2
Using the Factory Method Creational Pattern
There are three categories of patterns: behavioral, creational, and structural. Behavioral patterns define how objects interact; creational patterns define how objects are created; and structural patterns deal with class composition. The factory method is a creational pattern.
The factory method pattern manages the creation of related objects based on some criteria or a compound creational process. For example, the factory method helps when you need to perform several operations to create an object and ensure its proper initialization. In this example, for criteria like the text the user enters and the value that text represents, the factory method can contain the logic in one place to figure out which object to create. As Listing 1 shows, this example checks every possible command to see whether the command-line arguments represent a command by calling the static (Shared) Wants method on each command class. If it doesn't find a suitable command, an instance of the NullCommand is returned.
A final benefit is that no matter how many commands are ultimately created, the code for instantiating that command is modified in one place: the CommandFactory.Create method. Getting an instance of a command object looks the same throughout your application.
Using the Singleton Creational Pattern
Another creational pattern is the singleton pattern. Singleton's job is to ensure that only one instance of an object exists forever. It easily accomplishes this by using a non-public modifier on the constructor (Sub New) and a static method to access an instance of that class. As a static method, it can check to see whether an internal instance to the object exists. If the singleton object doesn't exist, the static method—as a member—can access the constructor and create the one and only instance. If the object does exist, the instance is returned.
The chat example uses the singleton pattern to ensure that only one instance of the Client class is created. In the elided example in Listing 2, you can see how the singleton is implemented as described by the private Client.New method and the public, shared, read-only Instance property.
Listing 2: The Singleton Pattern Ensures That Only One Instance of the Client Class Is Created Per Client Application Instance
Public Class Client
Inherits MarshalByRefObject
Implements IDisposable
Private FChatter As Chatter
Private Shared FClient As Client = Nothing
Private Sub New()
RemotingConfiguration.Configure("client.exe.config")
FChatter = New Chatter
AddHandler FChatter.MessageEvent, AddressOf OnMessageEvent
End Sub
Private Shared ReadOnly Property Instance() As Client
Get
If (FClient Is Nothing) Then
FClient = New Client
End If
Return FClient
End Get
End Property
//...
Using the Observer Pattern
Programmers run into trouble when they start cross-referencing things like form references. For instance, Form1 creates Form2 so Form1 has a reference to Form2. Form2 wants to update a status bar on Form1, so Form2 in turn has a reference to Form1. All of this criss-crossing of references results in a tightly coupled implementation. The problem is exacerbated when object1 knows about object2 and vice-versa, and one of the objects is a Form and the other is a control that you want to install in the Toolbox or use in another application. Now, your control is aware of a specific form that exists in just one application, which means the first form's assembly is loaded by VS.NET and any other application that uses that control.
Because tightly coupled implementations have lots of problems, events (now called delegates) exist. Delegates are an example of an implementation of the observer pattern, which is another behavioral pattern. The observer pattern is also known as publish and subscribe. It is the publish-subscribe version of the observer pattern, which I originally called broadcaster and listener (or radio pattern) when I discovered it independently seven years ago this month. (I will use the broadcaster and listener metaphor, as I think it works best here.)
The basic idea of radio is that a station sends out a signal. Listeners can tune in and receive the signal and then tune out. The radio station doesn't have to—although it would like to—know who is listening when, and the listeners don't have to know where the station is physically located. The listeners tune in and receive the signal, and multiple listeners can listen at the same time.
In code, this would mean that the sender of a message doesn't have to know who is receiving the message (loose coupling), multiple listeners (a status bar, the event log, or something else) can tune in, and listeners can come and go with impunity.
All of this code can be implemented one time as a typed collection of listeners. The listeners are defined as an interface, and a centralized broadcaster keeps track of listeners and acts as a central repository for messages. Simply send a message to the broadcaster and anything tuned in will be sent a copy of the message. The originator and broadcaster don't care what the listeners do with the message or even who is listening. (Listing 3 defines the IListener interface; Listing 4 defines the ListenerCollection; and Listing 5 defines the Broadcaster.)
Listing 3: The IListener Interface
Public Interface IListener
ReadOnly Property Listening() As Boolean
Sub Listen(ByVal message As String)
Sub Listen(ByVal message As String, ByVal formatter As IFormatter)
End Interface
Listing 4: The Strongly Typed ListenerCollection
Imports System.Collections
Public Class ListenerCollection
Inherits CollectionBase
Default Public Property Item(ByVal index As Integer) As IListener
Get
Return (CType(List(index), IListener))
End Get
Set(ByVal Value As IListener)
List(index) = Value
End Set
End Property
Public Function Add(ByVal value As IListener) As Integer
Return List.Add(value)
End Function
Public Sub Remove(ByVal value As IListener)
List.Remove(value)
End Sub
End Class
Listing 5: The Broadcaster Receives and Sends Messages to All Listeners
Public Class Broadcaster
Private Shared FInstance As Broadcaster = Nothing
Private listeners As ListenerCollection
Protected Sub New()
listeners = New ListenerCollection
End Sub
Protected Shared ReadOnly Property Instance() As Broadcaster
Get
If (FInstance Is Nothing) Then
FInstance = New Broadcaster
End If
Return FInstance
End Get
End Property
Public Shared Function Add(ByVal listener As IListener) As Integer
Return Instance.listeners.Add(listener)
End Function
Public Shared Sub Remove(ByVal listener As IListener)
Instance.listeners.Remove(listener)
End Sub
Public Shared Sub Broadcast(ByVal format As String, _
ByVal ParamArray args() As Object)
Broadcast(String.Format(format, args))
End Sub
Public Shared Sub Broadcast(ByVal message As String)
Broadcast(message, New GenericFormatter)
End Sub
Public Shared Sub Broadcast(ByVal message As String, _
ByVal formatter As IFormatter)
Dim listener As IListener
For Each listener In Instance.listeners
If (listener.Listening) Then
listener.Listen(message, formatter)
End If
Next
End Sub
End Class
Note: The Broadcaster uses the singleton pattern to ensure that only one Broadcaster instances exists. This ensures every message and listener go to the same place.
Anything that implements IListener can be added to and removed from the Broadcaster's collection of listeners. When a message arrives from anywhere—because the static method Broadcaster.Broadcast was called—the Broadcaster iterates through every listener in the collection and repeats the message. This means that if a form implements IListener (the Listening property and the Listen methods), then that form can receive the message. It also means any class can listen. The elided class ClientApp in Listing 6 shows how the presentation layer in the chat sample listens for messages from the remote server without ever knowing that they came from the remote server.
Listing 6: Realizing the Radio Pattern in the Console Presentation Layer
Imports Softconcepts.ApplicationBlocks.RadioPattern
Class ClientApp
Implements IListener
Public Overloads Sub Listen(ByVal message As String) _
Implements IListener.Listen
Console.WriteLine(message)
End Sub
Public ReadOnly Property Listening() As Boolean _
Implements IListener.Listening
Get
Return True
End Get
End Property
Public Shared Sub Main()
With New ClientApp
.Run()
End With
End Sub
Public Sub Run()
Broadcaster.Add(Me)
ProcessCommand("Startup")
While (True)
Console.Write("chat>")
If (ProcessCommand(Console.ReadLine()) = False) _
Then Exit While
End While
ProcessCommand("Shutdown")
Broadcaster.Remove(Me)
End Sub
Public Overloads Sub Listen(ByVal message As String, _
ByVal formatter _
As Softconcepts.ApplicationBlocks.RadioPattern.IFormatter) _
Implements Softconcepts.ApplicationBlocks.RadioPattern.IListener.Listen
If (message.Equals("chat>")) Then
Console.Write(message)
Else
Console.WriteLine(formatter.ApplyFormatting(message))
End If
End Sub
//...
