Tuesday, January 15, 2008

Is that default namespace in XML bothering you?

I had one of my dev team members walk up to me some days back with this really interesting requirement. He had these XML files that contained similar information except that the elements were in different namespaces on those files. If you find yourself caught up in a situation like this, probably this blog entry might be useful. Once again, I will show a few ways of getting this done, pick the right one based on your requirements.

For starters, I will create an XML file that contains a Person node at the root and three nodes - Name that lies in the n1 namespace, Age that lies in the n2 namespace and Gender that is part of the default namespace.

A listing of the Sample.xml file

<?xml version="1.0" encoding="utf-8" ?>

<Person xmlns:n1="http://codeedifice.com/N1" xmlns:n2="http://codeedifice.com/N2" xmlns="http://codeedifice.com/N">

  <n1:Name>Alvin</n1:Name>

  <n2:Age>10</n2:Age>

  <Gender>Male</Gender>

</Person>



The example that I am listing here lists the top three approaches taken by developers who are still getting their hands dirty with XML that contains namespaces. The next three approaches are alternatives that you must work out for yourself and pick the one that suits your actual requirement

  Sub Main()

    Dim xdoc As XmlDocument = New XmlDocument

    xdoc.Load("Sample.xml")

 

    'Fails due to lack of namespace

    Console.WriteLine("Looking for Person Node(s) without a namespace manager...")

    Dim xNodLst1 As Xml.XmlNodeList = xdoc.SelectNodes("/Person")

    Console.WriteLine(Space(4) & "Found " & xNodLst1.Count & " matche(s)")

 

    Dim nsMgr As XmlNamespaceManager = New XmlNamespaceManager(xdoc.NameTable)

    nsMgr.AddNamespace("n1", "http://codeedifice.com/N1")

    nsMgr.AddNamespace("n2", "http://codeedifice.com/N2")

 

    'Fails since the namespaces do not include the default one

    Console.WriteLine("Looking for Person Node(s) with the namespace manager not including the default namespace...")

    Dim xNodLst2 As Xml.XmlNodeList = xdoc.SelectNodes("/Person", nsMgr)

    Console.WriteLine(Space(4) & "Found " & xNodLst1.Count & " matche(s)")

 

    nsMgr.AddNamespace("", "http://codeedifice.com/N")

    'Fails since the empty string namespace is not useful (quite contrary to popular vote)

    Console.WriteLine("Looking for Person Node(s) with the namespace manager including the default namespace as an empty string...")

    Dim xNodLst3 As Xml.XmlNodeList = xdoc.SelectNodes("/Person", nsMgr)

    Console.WriteLine(Space(4) & "Found " & xNodLst3.Count & " matche(s)")

 

    nsMgr.RemoveNamespace("", "http://codeedifice.com/N"'take the useless one out

    nsMgr.AddNamespace("ALVIN", "http://codeedifice.com/N")

    'Finally a working solution

    Console.WriteLine("Looking for Person Node(s) with the namespace manager including the default namespace as a unique value - ALVIN...")

    Dim xNodLst4 As Xml.XmlNodeList = xdoc.SelectNodes("/ALVIN:Person", nsMgr)  'Note how the dummy namespace is used

    Console.WriteLine(Space(4) & "Found " & xNodLst4.Count & " matche(s)")

 

    'A slightly more elegant solution that does not rely on the unique value ALVIN to be created by the developer

    Console.WriteLine("Looking for Person Node(s) without the namespace manager using the localName XPath function...")

    Dim xNodLst5 As Xml.XmlNodeList = xdoc.SelectNodes("/*[local-name()='Person']")

    Console.WriteLine(Space(4) & "Found " & xNodLst5.Count & " matche(s)")

 

    'Accessing the Name and Age nodes correctly will require the use of the namespace

    Console.WriteLine("Looking for Name Node(s) with the namespace manager using the localName XPath function...")

    Dim xNodLst6 As Xml.XmlNodeList = xdoc.SelectNodes("/*[local-name()='Person']/n1:Name", nsMgr)

    Console.WriteLine(Space(4) & "Found " & xNodLst6.Count & " matche(s)")

 

    'Accessing the Name and Age nodes in a namespace agnostic fashion

    'This implies that you are assuming that you do not care about the namespace and can be dangerous

    Console.WriteLine("Looking for Name Node(s) without the namespace manager using the localName XPath function...")

    Dim xNodLst7 As Xml.XmlNodeList = xdoc.SelectNodes("/*[local-name()='Person']/*[local-name()='Name']")

    Console.WriteLine(Space(4) & "Found " & xNodLst7.Count & " matche(s)")

 

    Console.ReadLine()

  End Sub



And a look at the results

Thursday, January 10, 2008

Get XML Nodes that do not have a specific attribute

Alright after those design patterns, this one might strike as a real lightweight entry. Recently, I needed a method to identify all XML nodes that did not have a specific atrribute. Well, the first approach was a simple IF clause to verify if the attribute object is nothing. This did a fine job till I realized that I could a better approach, call it a result of refactoring or just an afterthought.

Sample code

  Sub Main()

    'Sample XML island

    Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder

    sb.AppendLine("<items>")

    sb.AppendLine("  <item type='Pencil' qty='5' IsCanceled='1' />")

    sb.AppendLine("  <item type='Pen' qty='2' />")

    sb.AppendLine("  <item type='Book' qty='1' />")

    sb.AppendLine("  <item type='Pencil' qty='3' />")

    sb.AppendLine("</items>")

 

    Dim xDoc As Xml.XmlDocument = New Xml.XmlDocument

    xDoc.LoadXml(sb.ToString)

 

    Console.WriteLine("Original XML Content")

    Console.WriteLine(sb.ToString)

 

    'Original Strategy

    Console.WriteLine("Active list of items")

    Dim xNodLst As Xml.XmlNodeList = xDoc.SelectNodes("/items/item")

    For Each xNod As Xml.XmlNode In xNodLst

      If xNod.Attributes("IsCanceled") Is Nothing Then

        Console.WriteLine("{0} [{1}]", xNod.Attributes("type").Value, xNod.Attributes("qty").Value)

      End If

    Next

    Console.WriteLine()

 

    'Alternate Strategy

    Console.WriteLine("Active list of items (Alternate)")

    Dim xNodAltLst As Xml.XmlNodeList = xDoc.SelectNodes("/items/item[not (@IsCanceled)]")

    For Each xNod As Xml.XmlNode In xNodAltLst

      Console.WriteLine("{0} [{1}]", xNod.Attributes("type").Value, xNod.Attributes("qty").Value)

    Next

 

    Console.ReadLine()

  End Sub



In this example, I have eliminated nodes that do not have the IsCanceled attribute. The exact value of the attribute was not relevant to me, its existence was reason enough to have it eliminated from my result list.

A look at the execution result


Of course, if you wanted to include only nodes that had a specific attribute, you guessed right, just take the 'not' keyword out of the query -

Dim xNodAltLst As Xml.XmlNodeList = xDoc.SelectNodes("/items/item[@IsCanceled]")

Thursday, January 3, 2008

Template Method Design Pattern

Consider a business application where your application receives data in different representations – XML, Text file, SQL data table etc. You need to print this data in a tabular format with a common header, footer, page size settings among other things. We can quickly cook up a couple of designs to accomplish this. At the simplest level, we can create a single module that prints the header, then based on the appropriate type of data, extracts the information from the data representations and prints individual rows of data, then prints the footer.

PrintFormatter Class Listing

Public Class PrintFormatter

 

  Public Enum DataRepresentation

    TextFormat

    XMLFormat

    SQLDataTableFormat

  End Enum

 

  Private _TextData As String

  Private _XMLData As Xml.XmlDocument

  Private _SQLData As DataTable

  Private _DataRepresentation As DataRepresentation

 

  Public Sub New(ByVal PrintDataRepresentation As DataRepresentation)

    _DataRepresentation = PrintDataRepresentation

  End Sub

 

  Public WriteOnly Property TextData() As String

    Set(ByVal value As String)

      _TextData = value

    End Set

  End Property

 

  Public WriteOnly Property XMLData() As Xml.XmlDocument

    Set(ByVal value As Xml.XmlDocument)

      _XMLData = value

    End Set

  End Property

 

  Public WriteOnly Property SQLData() As DataTable

    Set(ByVal value As DataTable)

      _SQLData = value

    End Set

  End Property

 

  Public Sub PrintFormattedData()

    _PrintHeader()

    Select Case _DataRepresentation

      Case DataRepresentation.TextFormat

        _PrintTextData()

      Case DataRepresentation.XMLFormat

        _PrintXMLData()

      Case DataRepresentation.SQLDataTableFormat

        _PrintSQLData()

    End Select

    _PrintFooter()

  End Sub

 

  Private Sub _PrintHeader()

    Console.WriteLine("This represents the page header")

  End Sub

 

  Private Sub _PrintFooter()

    Console.WriteLine("This represents the page footer")

  End Sub

 

  Private Sub _PrintTextData()

    Console.WriteLine("This represents the formatted text data - " & _TextData)

  End Sub

 

  Private Sub _PrintXMLData()

    Console.WriteLine("This represents the formatted XML data - " & _XMLData.OuterXml)

  End Sub

 

  Private Sub _PrintSQLData()

    Console.WriteLine("This represents the formatted SQL data - " & _SQLData.TableName)

  End Sub

 

End Class



Not much of a class diagram hierarchy to show



modMain Module Listing

Module modMain

 

  Sub Main()

 

    'Sample Data - one of each kind

    Dim TextData As String = "<<TextData>>"

    Dim XMLData As Xml.XmlDocument = New Xml.XmlDocument

    XMLData.LoadXml("<xMLData />")

    Dim SQLData As DataTable = New DataTable("SQLDataTable")

 

    Dim oPrintFormatter As PrintFormatter

    oPrintFormatter = New PrintFormatter(PrintFormatter.DataRepresentation.TextFormat)

    oPrintFormatter.TextData = TextData

    oPrintFormatter.PrintFormattedData()

 

    oPrintFormatter = New PrintFormatter(PrintFormatter.DataRepresentation.XMLFormat)

    oPrintFormatter.XMLData = XMLData

    oPrintFormatter.PrintFormattedData()

 

    oPrintFormatter = New PrintFormatter(PrintFormatter.DataRepresentation.SQLDataTableFormat)

    oPrintFormatter.SQLData = SQLData

    oPrintFormatter.PrintFormattedData()

 

    Console.ReadLine()

  End Sub

 

End Module



Let’s put our application to the test



Alright, things look good for a one off application requirement. However, extending this design to support additional data formats would require modifying the PrintFormatter class by adding additional data representations and modifying the PrintFormattedData method. A template method pattern can be effectively used in this example to eliminate the need to modify the base class and extending the base class to support additional data formats.
The primary idea behind the template method is to use a method to perform the actions that are common across representations – printing the header and footer while leaving the task of printing specific data representations to abstract methods. The PrintFormatter class can then be extended by the TextPrintFormatter to provide the implementation for printing text data. Similarly the XMLPrintFormatter can implement the abstract PrintSpecificData method to print XML Data while the SQLPrintFormatter can provide the SQL data printing logic.

PrintFormatter Class Listing

Public MustInherit Class PrintFormatter

 

  'Our Template method

  Public Sub PrintFormattedData()

    PrintHeader()

    PrintSpecificData()

    PrintFooter()

  End Sub

 

  'Some methods that provide a default implementation

  Public Sub PrintHeader()

    Console.WriteLine("This represents the page header")

  End Sub

 

  Public Sub PrintFooter()

    Console.WriteLine("This represents the page footer")

  End Sub

 

  'Abstract methods

  Public MustOverride Sub PrintSpecificData()

 

End Class



TextPrintFormatter Class Listing

Public Class TextPrintFormatter

  Inherits PrintFormatter

 

  Private _TextData As String

 

  Public Sub New(ByVal TextData As String)

    _TextData = TextData

  End Sub

 

  Public Overrides Sub PrintSpecificData()

    Console.WriteLine("This represents the formatted text data - " & _TextData)

  End Sub

End Class



XMLPrintFormatter Class Listing

Public Class XMLPrintFormatter

  Inherits PrintFormatter

 

  Private _XMLData As Xml.XmlDocument

 

  Public Sub New(ByVal XMLData As Xml.XmlDocument)

    _XMLData = XMLData

  End Sub

 

  Public Overrides Sub PrintSpecificData()

    Console.WriteLine("This represents the formatted XML data - " & _XMLData.OuterXml)

  End Sub

End Class



SQLDataPrintFormatter Class Listing

Public Class SQLDataPrintFormatter

  Inherits PrintFormatter

 

  Private _SQLData As DataTable

 

  Public Sub New(ByVal SQLData As DataTable)

    _SQLData = SQLData

  End Sub

 

  Public Overrides Sub PrintSpecificData()

    Console.WriteLine("This represents the formatted SQL data - " & _SQLData.TableName)

  End Sub

End Class



The Template method pattern class hierarchy



modMain Module Listing

Module modMain

 

  Sub Main()

    'Sample Data - one of each kind

    Dim TextData As String = "<<TextData>>"

    Dim XMLData As Xml.XmlDocument = New Xml.XmlDocument

    XMLData.LoadXml("<xMLData />")

    Dim SQLData As DataTable = New DataTable("SQLDataTable")

 

    Dim oPrintFormatter As PrintFormatter

 

    oPrintFormatter = New TextPrintFormatter(TextData)

    oPrintFormatter.PrintFormattedData()

 

    oPrintFormatter = New XMLPrintFormatter(XMLData)

    oPrintFormatter.PrintFormattedData()

 

    oPrintFormatter = New SQLDataPrintFormatter(SQLData)

    oPrintFormatter.PrintFormattedData()

 

    Console.ReadLine()

  End Sub

 

End Module



And a look at the results

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.

Adapter Design Pattern

Let us extend our simulation game example and introduce some animals in addition to the Human characters. For example, Trained elephants or Elephants for short. These trained elephants share a common behavior with other animals, say, Sleep, Drink, Eat and Graze. These elephants, being trained for warfare can also Move and Fight similar to Human characters. For this example, I will consider the Soldier who implements the Eat, Talk, Fight and Move behavior from the GameCharacter interface.

GameCharacter Interface Listing

Public Interface GameCharacter

  Sub Eat()

  Sub Talk()

  Sub Fight()

  Sub Move()

End Interface



Soldier Class Listing

Public Class Soldier

  Implements GameCharacter

 

  Public Sub Eat() Implements GameCharacter.Eat

    Console.WriteLine("Soldier is eating")

  End Sub

 

  Public Sub Fight() Implements GameCharacter.Fight

    Console.WriteLine("Soldier is fighting")

  End Sub

 

  Public Sub Move() Implements GameCharacter.Move

    Console.WriteLine("Soldier is moving slowly")

  End Sub

 

  Public Sub Talk() Implements GameCharacter.Talk

    Console.WriteLine("Soldier is talking")

  End Sub

End Class



Let us assume that the Animal class and the inheriting sub-classes for specific animals were created by another developer on the team.

Animal Class Listing

Public MustInherit Class Animal

  Public Sub Sleep()

    Console.WriteLine("Animal is sleeping")

  End Sub

 

  Public Sub Drink()

    Console.WriteLine("Animal is drinking")

  End Sub

 

  Public Sub Eat()

    Console.WriteLine("Animal is eating")

  End Sub

 

  Public Sub Graze()

    Console.WriteLine("Animal is grazing")

  End Sub

 

  Public Sub MakeSound()

    Console.WriteLine("Animal is making some noise")

  End Sub

End Class



The TrainedElephant class extends the Animal class by adding the Move and Attack methods

TrainedElephant Class Listing

Public Class TrainedElephant

  Inherits Animal

 

  Public Sub Move()

    Console.WriteLine("TrainedElephant is moving")

  End Sub

 

  Public Sub Attack()

    Console.WriteLine("TrainedElephant is attack")

  End Sub

End Class



Let us have a quick look at our class and interface hierarchy



And finally, lets list our requirement. With an existing design that resembles this, we would like to maintain a list of all character instances in the game including humans as well as animals that can fight and move. A quick solution is to use a generic list that holds GameCharacter objects. Since Soldiers natively implement the GameCharacter interface, it is quite easy to add a Soldier to this list. However, there is no relation between the Animal class and the GameCharacter interface and neither does the TrainedElephant class implement the Animal interface. It, therefore makes it impossible to add the TrainedElephant as a GameCharacter to the list.



One solution is to modify the code for the TrainedElephant class so that it implements the GameCharacter Interface. Although this seems easy from the looks of it, this may not be practical in a real life application where this portion of the code is built by another developer or when the source code for this class is not available for modification. In such scenarios, we can employ the services of the Adapter pattern to adapt the functionality of the TrainedElephant class to map to the GameCharacter class.
The TrainedElephantAdapter class simply wraps around a TrainedElephant object while implementing the GameCharacter interface. Thus, we do not need to modify the source code for the TrainedElephant or the Animal class and can use this TrainedElephantAdapter object interchangeably as a GameCharacter.

TrainedElephantAdapter Class Listing

Public Class TrainedElephantAdapter

  Implements GameCharacter

 

  Private _oElephant As TrainedElephant

 

  Public Sub New(ByVal oElephant As TrainedElephant)

    _oElephant = oElephant

  End Sub

 

  Public Sub Eat() Implements GameCharacter.Eat

    _oElephant.Eat()

  End Sub

 

  Public Sub Fight() Implements GameCharacter.Fight

    _oElephant.Attack()

  End Sub

 

  Public Sub Move() Implements GameCharacter.Move

    _oElephant.Move()

  End Sub

 

  Public Sub Talk() Implements GameCharacter.Talk

    _oElephant.MakeSound()

  End Sub

End Class




Our class diagram now contains our new TrainedElephantAdapter which encapsulates the TrainedElephant object while implementing the GameCharacter interface



Finally, a little test harness using the TrainedElephantAdapter to do the magic for us

modMain Module Listing

Module modMain

  Sub Main()

    Dim GameCharacters As List(Of GameCharacter) = New List(Of GameCharacter)

    GameCharacters.Add(New Soldier)

    'And here is the Wrapped up TrainedElephant

    GameCharacters.Add(New TrainedElephantAdapter(New TrainedElephant))

 

    For Each oGC As GameCharacter In GameCharacters

      oGC.Eat() : oGC.Fight() : oGC.Move() : oGC.Talk()

    Next

    Console.ReadLine()

  End Sub

End Module



And the results of using the Adapter pattern

Facade Design Pattern

The Facade pattern is probably one of the most frequently used design patterns. When a class requires the functionality derived from different classes, it is often useful to use a façade class that encapsulates the calls to the various classes. This class can then be effectively used instead of making calls to the individual classes and improves code reuse. Additionally, if we decide to make changes to the encapsulated classes, all we need to alter are the method invocations in the façade class.
Consider a business application for a mortgage company. The Mortgage Company uses multiple services that are both internal to the company as well as require interaction with other parties to determine if a loan can be sanctioned. Using the Facade pattern here is easy, create a class say LoanHelper. We can then create shared method unless you prefer to create an instance of the LoanHelper. Add the method calls to the different internal and external services. This LoanHelper method can then be used anywhere within your code to validate a prospect before allotting a loan and determining the rate of interest.

LoanHelper Class Listing

Public Class LoanHelper

 

  Public Const DEFAULT_LOAN_PERCENT As Double = 10.0

 

  Public Shared Function GetEligbleLoanCriteria(ByVal SSN As String, ByVal RequiredLoanAmount As Double, _

  ByRef EligibleLoanAmount As Double, ByRef EligibleLoanPercent As Double) As Boolean

    'Add the method calls to various systems - internal and external to determine

    'the values of EligibleLoanAmount and the EligibleLoanAmount

    EligibleLoanAmount = 100000

    EligibleLoanPercent = 8.25

    Console.WriteLine("Façade class LoanHelper was invoked to determine Eligible Loan Criteria")

    Return True  'Return false if SSN failed to match

  End Function

End Class



modMain Module Listing

Module modMain

  Sub Main()

    Dim SSN As String = "SampleSSN"

    Dim RequiredLoanAmount As Double = 100000

    Dim EligibleLoanAmount As Double = 0

    Dim EligibleLoanPercent As Double = LoanHelper.DEFAULT_LOAN_PERCENT

    Dim IsSSNMatch As Boolean = LoanHelper.GetEligbleLoanCriteria(SSN, RequiredLoanAmount, _

    EligibleLoanAmount, EligibleLoanPercent)

    Console.ReadLine()

  End Sub

End Module



A look at the classes


And the results

State Design Pattern

For this example, let us assume that we would like to design an application that simulates the interaction in a single character game. The character, in this game, can be moving on the ground, swimming under water, flying an airplane or driving a car. Each gaming episode may involve the character in any one of these conditions. The character is expected to navigate in these different environments using the same navigation keys on the keyboard. Additionally, the character may use the Action Key (say Control key on the keyboard) to perform actions that are relevant to each environment. While on ground, the Action Key is used to talk; while in water, it is used to float up for a breath of air, while in an airplane, to get directions and while in the car, to use the GPS.
A map of the keys versus environments is listed here

Key Ground Underwater Airplane Car 
Front Direction Move Forward Swim Forward Increase Throttle Accelerate 
Back Direction Move Backward Swim Backward Decrease Throttle Decelerate 
Right Direction Move Angular Right Swim Angular Right Change Direction to the Right Turn Car to the Right 
Left Direction Move Angular Left Swim Angular Left Change Direction to the Left Turn Car to the Left 
Action Talk Surface for Air Get directions Use GPS 

A first take at this design would be by starting off creating a GameCharacter class. The interactions that are possible would be based on the key strokes that the GameCharacter supports. Let me map them to the normal gaming keys on the keyboard – UpArrowPressed, DownArrowPressed, RightPressed and LeftPressed to cover the navigation areas and the ActionPressed method for supporting the character’s action feature. The State of the environment can be stored in an instance value that holds an enumeration.

GameCharacter Class Listing

Public Class GameCharacter

  Private Enum EnvironmentStates

    Land

    Underwater

    Airplane

    Car

  End Enum

 

  Private _EnvironmentState As EnvironmentStates

 

  Public Sub New()

    'All episodes start with the character on Land

    _EnvironmentState = EnvironmentStates.Land

  End Sub

 

  Public Sub UpArrowPressed()

    Select Case _EnvironmentState

      Case EnvironmentStates.Land

        Console.WriteLine("Character is moving forward")

      Case EnvironmentStates.Underwater

        Console.WriteLine("Character is swimming forward")

      Case EnvironmentStates.Airplane

        Console.WriteLine("Character is increasing throttle")

      Case EnvironmentStates.Car

        Console.WriteLine("Character is accelerating")

    End Select

  End Sub

 

  Public Sub DownArrowPressed()

    Select Case _EnvironmentState

      Case EnvironmentStates.Land

        Console.WriteLine("Character is moving backwards")

      Case EnvironmentStates.Underwater

        Console.WriteLine("Character is swimming backwards")

      Case EnvironmentStates.Airplane

        Console.WriteLine("Character is decreasing throttle")

      Case EnvironmentStates.Car

        Console.WriteLine("Character is decelerating")

    End Select

  End Sub

 

  Public Sub RightArrowPressed()

    Select Case _EnvironmentState

      Case EnvironmentStates.Land

        Console.WriteLine("Character is moving at an angular right")

      Case EnvironmentStates.Underwater

        Console.WriteLine("Character is swimming at an angular right")

      Case EnvironmentStates.Airplane

        Console.WriteLine("Character is changing directionto the right")

      Case EnvironmentStates.Car

        Console.WriteLine("Character is turning car to the right")

    End Select

  End Sub

 

  Public Sub LeftArrowPressed()

    Select Case _EnvironmentState

      Case EnvironmentStates.Land

        Console.WriteLine("Character is moving at an angular left")

      Case EnvironmentStates.Underwater

        Console.WriteLine("Character is swimming at an angular left")

      Case EnvironmentStates.Airplane

        Console.WriteLine("Character is changing directionto the left")

      Case EnvironmentStates.Car

        Console.WriteLine("Character is turning car to the left")

    End Select

  End Sub

 

  Public Sub ActionPressed()

    Select Case _EnvironmentState

      Case EnvironmentStates.Land

        Console.WriteLine("Character is talking")

      Case EnvironmentStates.Underwater

        Console.WriteLine("Character is surfacing for air")

      Case EnvironmentStates.Airplane

        Console.WriteLine("Character is getting directions")

      Case EnvironmentStates.Car

        Console.WriteLine("Character is using the GPS")

    End Select

  End Sub

End Class



Additionally, while in water, if the character reaches the edge of the water body, user will jump out of water and be on solid ground. Similarly, reaching the landing area, whilst in an airplane, will move the character on the ground. In a similar fashion, the character will change environments when he reaches the periphery of that environment. Ground is compatible with all the three environments and changes are triggered in both directions on reaching appropriate boundaries. Environment changes between other boundaries are not possible.

Environment Water boundary Airport boundary Parking Lot boundary
Land Jump underwater Jump inside Airplane Jump inside Car
Underwater Jump on Land* Not Applicable Not Applicable
Airplane Not Applicable Jump on Land* Not Applicable
Car Not Applicable Not Applicable Jump on Land*

* Next corresponding boundary

We can extend our design to add instance variables that can help define the boundary parameters. Note that while on land – a two dimensional co-ordinate system can be used to map the boundary, however while in water or in air, we need a three dimensional co-ordinate system to store the depth and height respectively in addition to the x and y co-ordinates.

Additional Instance variables

  Private _XCoord As Integer

  Private _YCoord As Integer

  Private _DepthCoord As Integer

  Private _HeightCoord As Integer

  Private _OpenLandXDimension As Integer

  Private _OpenLandYDimension As Integer

  Private _WaterXDimension As Integer

  Private _WaterYDimension As Integer

  Private _WaterDepthDimension As Integer

  Private _AirplaneXDimension As Integer

  Private _AirplaneYDimension As Integer

  Private _AirplaneHeightDimension As Integer

  Private _RoadXDimension As Integer

  Private _RoadYDimension As Integer



Modified constructor

  Public Sub New()

    'All episodes start with the character on Land

    _EnvironmentState = EnvironmentStates.Land

    ' ... at location 0,0; isn't that nice.

    _XCoord = 0 : _YCoord = 0 : _DepthCoord = 0 : _HeightCoord = 0

 

    'Define the dimensions of each environment

    _OpenLandXDimension = 100 : _OpenLandYDimension = 100

    _WaterXDimension = 100 : _WaterYDimension = 100 : _WaterDepthDimension = 50

    _AirplaneXDimension = 100 : _AirplaneYDimension = 100 : _AirplaneHeightDimension = 200

    _RoadXDimension = 50 : _RoadYDimension = 200

  End Sub



Boundary determining functions

  Private Function _IsWaterBoundaryEncountered() As Boolean

    'Use the layout map and the current X and Y coordinates and the Depth coordinate to determine if the water boundary is encountered

    'the depth coordinate is useful when the current state is in water

  End Function

 

  Private Function _IsAirportBoundaryEncountered() As Boolean

    'Use the layout map and the current X and Y coordinates as well as the Height coordinate to determine if the airport boundary is encountered

    'the height coordindate is useful when the current state is in the airplane

  End Function

 

  Private Function _IsParkingLotBoundaryEncountered() As Boolean

    'Use the layout map and the current X and Y coordinates as well as the coordinate to determine if the parking lot boundary is encountered

  End Function



Partially modified listing of the UpArrowPressed method

  Public Sub UpArrowPressed()

    Select Case _EnvironmentState

      Case EnvironmentStates.Land

        Console.WriteLine("Character is moving forward")

        _XCoord += 1

 

        Select Case True

          Case _IsWaterBoundaryEncountered()

            _EnvironmentState = EnvironmentStates.Underwater

            _XCoord = 0 : _YCoord = 0 : _DepthCoord = 1

 

          Case _IsAirportBoundaryEncountered()

            _EnvironmentState = EnvironmentStates.Airplane

            _XCoord = 0 : _YCoord = 0 : _HeightCoord = 1

 

          Case _IsParkingLotBoundaryEncountered()

            _EnvironmentState = EnvironmentStates.Car

            _XCoord = 0 : _YCoord = 0

        End Select

 

      Case EnvironmentStates.Underwater

        Console.WriteLine("Character is swimming forward")

        'Repeat similar code here

 

      Case EnvironmentStates.Airplane

        Console.WriteLine("Character is increasing throttle")

        'Repeat similar code here

 

      Case EnvironmentStates.Car

        Console.WriteLine("Character is accelerating")

        'Repeat similar code here

    End Select

  End Sub



Of course, besides navigating in these environments, the character performs a number of actions that are not relevant in the context of this example and I have deliberately avoided them (at least for now)
Well, we have a working design and things look good. A closer look at the code brings out the following issues –
  • The instance variable definition shows that a lot of variables only have contextual meaning based on the state of the character. For example, the _DepthCoord and the _HeightCoord have no meaning when the GameCharacter is on ground or while he is driving a car.

  • Each supported keystroke handler has the same “Select Case” code blocks to determine their action based on the state of the GameCharacter.

  • State transitions are interspersed in code making it almost impossible to understand when a transition takes place.

  • Adding a new state – say Sailing in a boat will require rework of each keystroke handler and additional rework on maintaining the correct state when the boundary is encountered. This implies that the entire GameCharacter class will need to be checked for changes and at least five methods will need to be altered in addition to adding instance variables to represent state specific behavior.

So how do we fix this? Quite easily, as a matter of fact, all we need to do is build a state interface that provides methods to address the keystrokes and implement each required state to handle the keystrokes as it sees fit in its environment. The GameCharacter simply holds the instance of the current state and delegates all keystrokes to the current state instance.

IEnvironmentState Interface Listing

Public Interface IEnvironmentState

  Sub UpArrowPressed()

  Sub DownArrowPressed()

  Sub RightArrowPressed()

  Sub LeftArrowPressed()

  Sub ActionPressed()

End Interface



Move the responsibility of holding and determining the environment boundaries to the GameEnvironmentManager class

GameEnvironmentManager Class Listing

Public Class GameEnvironmentManager

 

  Private _OpenLandXDimension As Integer

  Private _OpenLandYDimension As Integer

  Private _WaterXDimension As Integer

  Private _WaterYDimension As Integer

  Private _WaterDepthDimension As Integer

  Private _AirplaneXDimension As Integer

  Private _AirplaneYDimension As Integer

  Private _AirplaneHeightDimension As Integer

  Private _RoadXDimension As Integer

  Private _RoadYDimension As Integer

 

  Public Sub New()

    'Define the dimensions of each environment

    _OpenLandXDimension = 100 : _OpenLandYDimension = 100

    _WaterXDimension = 100 : _WaterYDimension = 100 : _WaterDepthDimension = 50

    _AirplaneXDimension = 100 : _AirplaneYDimension = 100 : _AirplaneHeightDimension = 200

    _RoadXDimension = 50 : _RoadYDimension = 200

  End Sub

 

  Public Function IsWaterBoundaryEncountered(ByVal XCoord As Integer, ByVal YCoord As Integer) As Boolean

    'Use the X and Y coord to determine if the Water boundary is encountered

  End Function

 

  Public Function IsWaterBoundaryEncountered(ByVal XCoord As Integer, ByVal YCoord As Integer, ByVal DepthCoord As Integer) As Boolean

    'Use the X and Y coord alongwith the depth to determine if the Water boundary is encountered

  End Function

 

  'Other functions to determine the other boundaries

End Class



Implement each state and control the state of the GameCharacter appropriately by creating classes for WalkingLand, SwimmingUnderWater, FlyingAirplane and DrivingCar

WalkingLand Class Listing

Public Class WalkingLand

  Implements IEnvironmentState

 

  Private _oGameCharacter As GameCharacter

  Private _oGEM As GameEnvironmentManager

  Private _XCoord As Integer

  Private _YCoord As Integer

 

  Public Sub New(ByVal oGameCharacter As GameCharacter, ByVal oGEM As GameEnvironmentManager)

    _oGameCharacter = oGameCharacter

    _oGEM = oGEM

    _XCoord = 0

    _YCoord = 0

  End Sub

 

  Public Sub UpArrowPressed() Implements IEnvironmentState.UpArrowPressed

    Console.WriteLine("Character is moving forward")

    _XCoord += 1

    _HandleIfBoundaryEncountered()

  End Sub

 

  Public Sub DownArrowPressed() Implements IEnvironmentState.DownArrowPressed

    Console.WriteLine("Character is moving backwards")

    _XCoord -= 1

    _HandleIfBoundaryEncountered()

  End Sub

 

  Public Sub RightArrowPressed() Implements IEnvironmentState.RightArrowPressed

    Console.WriteLine("Character is moving at an angular right")

    _XCoord += 1 : _YCoord -= 1

    _HandleIfBoundaryEncountered()

  End Sub

 

  Public Sub LeftArrowPressed() Implements IEnvironmentState.LeftArrowPressed

    Console.WriteLine("Character is moving at an angular left")

    _XCoord += 1 : _YCoord += 1

    _HandleIfBoundaryEncountered()

  End Sub

 

  Public Sub ActionPressed() Implements IEnvironmentState.ActionPressed

    Console.WriteLine("Character is talking")

  End Sub

 

  Private Sub _HandleIfBoundaryEncountered()

    Select Case True

      Case _oGEM.IsWaterBoundaryEncountered(_XCoord, _YCoord)

        _oGameCharacter.EnvironmentState = New SwimmingUnderwater(_oGameCharacter, _oGEM)

 

        'Other boundary encountered statements go here

    End Select

  End Sub

End Class



GameCharacter Class Listing

Public Class GameCharacter

  Private _oEnvironmentState As IEnvironmentState

  Private _oGEM As GameEnvironmentManager

 

  Public Sub New()

    _oGEM = New GameEnvironmentManager

    'All episodes start with the character on Land

    _oEnvironmentState = New WalkingLand(Me, _oGEM)

  End Sub

 

  Public Sub UpArrowPressed()

    _oEnvironmentState.UpArrowPressed()

  End Sub

 

  Public Sub DownArrowPressed()

    _oEnvironmentState.DownArrowPressed()

  End Sub

 

  Public Sub RightArrowPressed()

    _oEnvironmentState.RightArrowPressed()

  End Sub

 

  Public Sub LeftArrowPressed()

    _oEnvironmentState.DownArrowPressed()

  End Sub

 

  Public Sub ActionPressed()

    _oEnvironmentState.ActionPressed()

  End Sub

 

  Public WriteOnly Property EnvironmentState() As IEnvironmentState

    Set(ByVal value As IEnvironmentState)

      _oEnvironmentState = value

    End Set

  End Property

End Class



I have ignored the details of the other classes to reduce the code bulk in this example. This design completely separates the GameCharacter from its constituent states and the behavior of the GameCharacter is delegated to the individual states. Interestingly, each state may need to be aware of other related transitioning states in the system so that a state transition can be effectively addressed. An alternative to our design is to have these states predefined in the GameCharacter class and simply trigger the GameCharacter class via public methods to set its next state appropriately. This alternate design will eliminate any need for the implementing EnvironmentStates to know about each other and the entire controlling mechanism is built in the GameCharacter class.
Selecting an appropriate design choice depends on how best you feel about handling additional states or packaging components.



I have built a test harness to test our GameCharacter and its reaction based on Keystrokes. For the sake of keeping the example short, I have forced a return value of true for the boundary condition tests when the GameCharacter moves forward and when it surfaces for air after swimming backward.

modMain Module Listing

Module modMain

  Sub Main()

    Dim oGameCharacter As GameCharacter = New GameCharacter

    oGameCharacter.UpArrowPressed()  'This will trigger Swimming in water

    oGameCharacter.DownArrowPressed()

    oGameCharacter.ActionPressed() 'This will trigger Jumping on Land

    oGameCharacter.UpArrowPressed()  'This will trigger Swimming in water again, but we wont test it

    Console.ReadLine()

  End Sub

End Module



And the results