This final installment of the .NET Remoting and Event Handling in VB .NET series takes a closer look at some of the supporting code in the simple chat client and server application that Parts 1 and 2 demonstrated, including the use of the command, observer, singleton, and factory patterns. The previous installments also externalized the text for the client help by using an XML resource file and the resource manager, a feature that supports internationalization. Part 3 quickly covers that code as well.
Programming with Patterns
Patterns are general design solutions to well-known problems. The best-known patterns are the Gang of Four (GoF) patterns defined in Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Help, Ralph Johnson, and John Vlissides. Much has been written about patterns by these four and others.
Basically, patterns are reusable designs. The designs are clearly documented, believed, and demonstrated to solve the problems they are purported to solve when used properly, and commonly found in well-architected applications. The Design Patterns book (from Addison-Wesley) is the definitive resource on these patterns; I won’t regurgitate everything in it. Instead, I show just the implementation of these patterns in the chat demo from Parts 1 and 2 with brief explanations of each one and how I used it.
Using the Command Behavior Pattern
The command pattern is the encapsulation of actions. One of the most well-known uses is supporting an undo behavior. Good frameworks such as Delphi’s VCL use the command pattern for common actions like File|Open, Edit|Cut, and so on. The .NET Framework does not to date. (I suspect .NET will encapsulate commands for WinForms very soon.)
Just because the command pattern isn’t part of .NET doesn’t mean you can’t use it. It’s very simple: When you want to define an action, just encapsulate it in a class and attach an instance of the class to your GUI through event handlers or invoke the action from the GUI’s events. The former is probably a better use of command objects.
Once you’ve bundled your commands in a class, supporting an undo behavior is easy—code the opposite action and queue the commands—as is doing things like log actions or even automating them with a macro-like language.
I defined several commands in the chat presentation layer. Listing 1 shows the complete listing for the commands.
Listing 1: The Commands Defined by the Presentation Layer for the Remoting Chat Sample
Imports System.Resources Imports System.Security.Principal Imports System.Text.RegularExpressions Imports Softconcepts.ApplicationBlocks.RadioPattern Imports SharedCode Public Class CommandFactory Public Shared Function Create(ByVal input As String) As Command If (Quit.Wants(input)) Then Return New Quit If (Help.Wants(input)) Then Return New Help If (Send.Wants(input)) Then Return New Send If (Startup.Wants(input)) Then Return New Startup If (History.Wants(input)) Then Return New History If (Shutdown.Wants(input)) Then Return New Shutdown If (NullCommand.Wants(input)) Then Return New NullCommand End Function End Class Public MustInherit Class Command Public MustOverride Function Execute(ByVal input As String) _ As Boolean End Class Public Class Quit Inherits Command Public Shared Function Wants(ByVal input As String) As Boolean Return input.ToUpper() = "Q" End Function Public Overrides Function Execute(ByVal input As String) As Boolean Return False End Function End Class Public Class Help Inherits Command Private Shared resourceManager As resourceManager = Nothing Shared Sub New() If (resourceManager Is Nothing) Then resourceManager = New ResourceManager("Client.Help", _ System.Reflection.Assembly.GetExecutingAssembly()) End If End Sub Public Shared Function Wants(ByVal input As String) As Boolean Return input = "?" Or Regex.IsMatch(input, "^help", _ RegexOptions.IgnoreCase) End Function Public Overrides Function Execute(ByVal input As String) As Boolean Try Broadcaster.Broadcast(resourceManager.GetString("HELP")) Catch Broadcaster.Broadcast("Help not available") End Try Return True End Function End Class Public Class NullCommand Inherits Command Public Shared Function Wants(ByVal input As String) As Boolean Return True End Function Public Overrides Function Execute(ByVal input As String) As Boolean Broadcaster.Broadcast(String.Format("Command invalid: '{0}'", input)) Return True End Function End Class Public Class Send Inherits Command Public Shared Function Wants(ByVal input As String) As Boolean Return Regex.IsMatch(input, "^send ", RegexOptions.IgnoreCase) End Function Public Overrides Function Execute(ByVal input As String) As Boolean Dim message As String = input.Remove(0, 4).Trim() Broadcaster.Broadcast(String.Format("Sending '{0}'", message)) SharedCode.Client.Send(WindowsIdentity.GetCurrent().Name, message) Return True End Function Private Function IsSelf(ByVal sender As String) Return sender = WindowsIdentity.GetCurrent().Name End Function End Class Public Class Startup Inherits Command Public Shared Function Wants(ByVal input As String) As Boolean Return Regex.IsMatch(input, "^startup$", RegexOptions.IgnoreCase) End Function Public Overrides Function Execute(ByVal input As String) As Boolean SharedCode.Client.Send(WindowsIdentity.GetCurrent().Name, "hello") Dim helper As Help = New Help helper.Execute("help") Return True End Function End Class Public Class Shutdown Inherits Command Public Shared Function Wants(ByVal input As String) As Boolean Return Regex.IsMatch(input, "^shutdown$", RegexOptions.IgnoreCase) End Function Public Overrides Function Execute(ByVal input As String) As Boolean Broadcaster.Broadcast("Goodbye") SharedCode.Client.Shutdown() End Function End Class Public Class History Inherits Command Public Shared Function Wants(ByVal input As String) As Boolean Return Regex.IsMatch(input, "^history$", RegexOptions.IgnoreCase) End Function Public Overrides Function Execute(ByVal input As String) As Boolean SharedCode.Client.ShowHistory() Return True End Function End Class
The commands defined are Quit, Help, NullCommand, Send, Startup, Shutdown, and History. Each of these commands is a class and provides the behavior defined by the command’s name. The NullCommand is actually a pattern (command behavior pattern) and a refactoring instruction (introduce null object). In this case, the NullCommand represents an invalid command, and the operation it invokes tells the user the command wasn’t recognized. Null objects are a good way to eliminate tests for null. For example, instead of checking to see whether a command is returned, I can always just call Execute. If the NullCommand is returned, Execute is still a valid method call and I know an invalid command was entered in the example.
Each of the command classes overrides the abstract method Execute. Additionally, I added a static method called Wants. The factory uses this to create the correct instance of a command based on the user input.
In the example, the Send command sends a message to the chat server. From the presentation implementer’s point of view, Send and all the other commands are equal. The result is a homogenization of action invocation. That is, a GUI program can invoke Send as easily as Quit without worrying about the nuts and bolts of remoting. This is a division of focus that promotes team development or at least a consistent style for one programmer (in other words, all behaviors are command objects.)