Thursday, November 4, 2010

Get last modified stored procedures on SQL 2005 and above

If you need to quickly get the list of all last modified stored procedures and the body of the stored procedure to aid in a quick update deployment from pre staging to production environment, try this stored procedure.


Create proc DevGetLastModifiedItems
(
@AfterTimeStamp as datetime
)
as
declare varCur cursor for
Select sys.objects.name, modify_date,
sys.all_sql_modules.definition
from sys.objects
inner join sys.all_sql_modules on sys.objects.object_id = sys.all_sql_modules.object_id
where sys.objects.type='P'
and sys.objects.modify_date >= @AfterTimeStamp
and sys.objects.name <> 'DevGetLastModifiedItems' -- Cannot see why this SP should ever turn up on a production server :)
order by modify_date desc
open varCur
declare @name as varchar(200),
@definition as varchar(max),
@modify_date as datetime
Fetch next from varCur into @name, @modify_date, @definition
while @@Fetch_status = 0
begin
Print '-- ' + @name + ' last modified on ' + convert(varchar(40), @modify_date, 9)
Print ''
Print @definition
Print 'go'
Print ''
Print ''
Fetch next from varCur into @name, @modify_date, @definition
end
close varCur
deallocate varCur
go


To get the complete list and body of the stored procedures that have been modified since a date say '2010-11-04', simply fire this on the Management Studio window and check out the contents of the messages window


exec DevGetLastModifiedItems '2010-11-04'

Wednesday, October 13, 2010

Expose Child Control In A Custom User Control For Javascript

Hit upon a javascript requirement. I have two controls on my page - a textbox control and a custom user control. The custom user control itself hosts two textboxes. The requirement wanted me to set the value of the standalone textbox control on one of the two inside textbox controls. Came up with two solutions

The unelegant one first - Add an attribute in the hosting page load to handle the blur event of the standalone textbox triggering a __doPostBack. In the load event of the custom user control, adding code to use the FindControl function on the parent page to retrieve the standalone textbox's value and store this in the inside textbox.
While this did the task, the actual postback of the page was a real spoiler (especially since my page had more than just these two items).

Relooked at the same issue a while back and thought of another solution.
An elegant one - Expose the inside textbox as a property of the usercontrol. In the hosting page, add an attribute to handle the blur event, this time however use a simple javascript to set the value of the textbox exposed as a property to the value of the standalone textbox.
Consider the standalone textbox is called txt1 and the usercontrol uc1 has two textboxes - txt2 and txt3. We want to set the value of txt3 to be the same as txt1. We have now exposed txt3 as a property of uc1 say txt3TextBox

In code,
Dim blurJS As String = String.Format(document.getElementByID('{0}').value = document.getElementByID('{1}').value, uc1.txt3TextBox.ClientID, txt1.ClientID)
txt1.Attributes.Add("OnBlur", blurJS)

Well, I had more fun than I added here especially with the txt3 control being part of a view of the multiview and hence I needed to add more checks to ensure that the left hand side of the expression was not null and such. But that is another story.

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