.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 //...