.NET Remoting and Event Handling in VB .NET, Part 3

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

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read