Chapter 15. The Adapter Pattern

The Adapter pattern is used to convert the programming interface of one class into that of another. We use adapters whenever we want unrelated classes to work together in a single program. The concept of an adapter is thus pretty simple: We write a class that has the desired interface and then make it communicate with the class that has a different interface.

There are two ways to do this: by inheritance and by object composition. In the first case, we derive a new class from the nonconforming one and add the methods we need to make the new derived class match the desired interface. The other way is to include the original class inside the new one and create the methods to translate calls within the new class. These two approaches, called class adapters and object adapters, are both fairly easy to implement in other languages, but before VB7 you were forced to use object composition preferentially, since inheritance was not available.

Moving Data between Lists

Let's consider a simple program that allows you to select some names from a list to be transferred to another list for a more detailed display of the data associated with them. Our initial list consists of a team roster, and the second list the names plus their times or scores.

In this simple program, shown in Figure 15-1, the program reads in the names from a roster file during initialization. To move names to the right-hand list box, you click on them and then click on the right arrow button. To remove a name from the right-hand list box, click on it and then on the left arrow button. This moves the name back to the left-hand list.

A simple program to choose names for display

Figure 15-1. A simple program to choose names for display

This is a very simple program to write in VB. It consists of the visual layout and action routines for each of the button clicks. When we read in the file of team roster data, we store each child's name and score in a Swimmer object and then store all of these objects in a collection. When you select one of the names to display in expanded form, you simply obtain the list index of the selected child from the left-hand list and get that child's data to display in the right-hand list.

Private Sub Moveit_Click()
Dim i As Integer
i = lsKids.ListIndex + 1
 If i > 0 And i <= swmrs.Count Then
    Set sw = swmrs(i)
    lsTimes.AddItem sw.getName & vbTab & str$(sw.getTime)
 End If
End Sub

In a similar fashion, if we want to remove a name from the right-hand list, we just obtain the selected index and remove the name.

Private Sub putback_Click()
 Dim i As Integer
 i = lsTimes.ListIndex
 If i >= 0 Then
   lsTimes.RemoveItem i
 End If
End Sub

Note that we obtain the column spacing between the two rows using the tab character. This works fine as long as the names are more or less the same length. However, if one name is much longer or shorter than the others, the list may end up using a different tab column, which is what happened for the third name in the list.

Using the MSFlexGrid

To circumvent this problem with the tab columns in the simple list box, we might turn to a grid display. One simple grid that comes with VB is called the MSFlexGrid. It is a subset of a more elaborate control available from a third-party vendor. The MSFlexGrid has Rows and Col properties that you can use to find out its current size. Then you can set the Row and Col properties to the row and column you want to change and use the Text property to change the text in the selected cell of the grid.

Private Sub Movetogrid_Click()
Dim i As Integer, row As Integer

i = lsKids.ListIndex + 1
 If i > 0 And i <= swmrs.Count Then
    Set sw = swmrs(i)
    grdTimes.AddItem ""
    row = grdTimes.Rows
    grdTimes.row = row - 1
    grdTimes.Col = 0
    grdTimes.Text = sw.getName
    grdTimes.Col = 1
    grdTimes.Text = Str$(sw.getTime)
 End If
End Sub

However, we would like to be able to use the grid without changing our code at all from what we used for the simple list box. It turns out that you can do that because the AddItem method of the MSFlexGrid interprets tab characters in an analogous fashion to the way the list box does.

The following statement

grdTimes.AddItem sw.getName & vbTab & Str$(sw.getTime)

works the same as the seven lines of code do that we showed in the previous example, and the resulting display will put the names in one column and the scores in the other, as shown in Figure 15-2.

Selecting names for display in an MSFlexGrid control

Figure 15-2. Selecting names for display in an MSFlexGrid control

In other words, the MSFlexGrid control provides the same programming interface as a convenience and is in fact its own Adapter between the list box and the MSFlexGrid control.

In fact, since the list and grid have the same programming interface, it is quite easy to write a private subroutine to add the data to either of them.

Private Sub addText(ctl As Control, sw As Swimmer)
    ctl.AddItem sw.getName & vbTab & str$(sw.getTime)
End Sub

Then we could write the button click routines so they each call this method using a different list as an argument.

Private Sub Moveit_Click()
 Dim i As Integer
 i = lsKids.ListIndex + 1
 If i > 0 And i <= swmrs.Count Then
    Set sw = swmrs(i)
    addText lsTimes, sw
 End If
End Sub
'------
Private Sub Movetogrid_Click()
 Dim i As Integer, row As Integer
 i = lsKids.ListIndex + 1
 If i > 0 And i <= swmrs.Count Then
    Set sw = swmrs(i)
    addText grdTimes, sw
 End If
End Sub

However, this is clearly not very object oriented. The addText method really should be part of the class we are using. We shouldn't have to pass an instance of the list or grid into a method in the same class. Now in VB6 and before, there is no way to add methods to a control. Instead, we can create a simple Control Adapter class that will handle both the grid and the list and contain the addText method we wrote as a simple subroutine above. This class is the following.

'Class ControlAdapter
Private ctrl As Control

Public Sub init(ctl As Control)
 Set ctrl = ctl  'copy control into class
End Sub
'-----
Public Sub addText(sw As Swimmer)
'add new line to list or grid
 ctrl.AddItem sw.getName & vbTab & str$(sw.getTime)
End Sub

We initialize this class with an instance of a list or grid in the Form_Load event.

Private grdAdapt As New ControlAdapter

'pass grid into Control Adapter
grdAdapt.init grdTimes

Then we can simply call the class's addText method when we click on the right arrow button, regardless of which display control we are using.

Private Sub Moveit_Click()
 Dim i As Integer
 i = lsKids.ListIndex + 1
 If i > 0 And i <= swmrs.Count Then
    Set sw = swmrs(i)
    grdAdapt.addText sw
 End If
End Sub

Using a TreeView

If, however, you choose to use a TreeView control to display the data you select, you will find that there is no conveniently adapted interface that you can use to keep your code from changing. Thus, our convenient ControlAdapter class can not be used for the TreeView. Instead, we need to write a new TreeAdapter class that has the same interface but carries out the adding of a line to the tree correctly.

TheTreeView class contains a Nodes collection to which you add data by adding a node, setting its text, and defining whether it is a child node. Child nodes are related to the index of the parent node. The following code adds a parent node and then adds a child node to it.

'Class TreeAdapter
Private Tree As TreeView
Public Sub init(tr As TreeView)
  Set Tree = tr
End Sub
Public Sub addText(sw As Swimmer)
Dim scnt As String, nod As Node
    scnt = Str$(Tree.Nodes.Count)
    Set nod = Tree.Nodes.Add(, tvwNext, "r" & _
        sw.getName, sw.getName)
    Tree.Nodes.Add "r" & sw.getName, tvwChild, , Str$(sw.getTime)
    nod.Expanded = True
End Sub

We illustrate our TreeView program in Figure 15-3.

The TreeView adapter program

Figure 15-3. The TreeView adapter program

The Object Adapter

In the object adapter approach, (Figure 15-4), we create a class that contains a List Box class but that implements the methods of the ControlAdapter interface. This is the approach we took in the preceding example.

An object adapter approach to the list adapter

Figure 15-4. An object adapter approach to the list adapter

Using Adapters in VB7

Adapters can be even more powerful in VB7. Let's first consider the VB7 ListBox itself. This control has rather different methods from the VB6 list box, and we might very well want to hide this difference by using an adapter so that the methods that are brought to the surface appear to be the same.

In VB7, you add a line to a list box by adding a String to the listbox's Items collection.

list.Items.Add(s)

The ListIndex property is replaced by the SelectedIndex property. So we could easily write a simple wrapper class that translates these methods into the ListBox methods for VB7. The beginning of a ListAdapter class has a ListBox instance in its constructor and saves that instance within the class, thus using encapsulation or object composition.

Public Class ListAdapter
    'Adapter for ListBox emulating some of
    'the methods of the VB6 list box.
    Private List As ListBox         'instance of list box
    '--------
    Public Sub New(ByVal ls As ListBox)
        List = ls
    End Sub
    '--------
    Public Sub addItem(ByVal s As String)
        list.Items.Add(s)   'add into list box
    End Sub
    '--------
    Public Function ListIndex() As Integer
        Return list.SelectedIndex     'get list index
    End Function
    '--------
    Public Sub addText(ByVal sw As Swimmer)
        List.Items.Add(sw.getName & vbTab & sw.getTime.ToString)
    End Sub
End Class

However, in the program we have been discussing, we want to display the name of the swimmer and the swimmer's time. Thus, it is convenient to add a method that takes a Swimmer object as an argument and puts the name and time in the list box.

The code that reads the swimmers in from the file and loads their names into the left-hand list box also uses an instance of the ListAdapter. Here are declarations and initialization.

Private lsAdapter, ksAdapter As ListAdapter

        lsAdapter = New ListAdapter(lsNames)
        ksAdapter = New ListAdapter(lsKids)

This simple routine reads in the lines of data.

Private Sub ReadFile()
        Dim s As String
        Dim sw As Swimmer
        Dim fl As New vbFile("swimmers.txt")
        fl.openForRead()
        s = fl.readLine
        While Not fl.fEof
            sw = New Swimmer(s)
            swimmers.add(sw)
            ksAdapter.addItem(sw.getName)
            s = fl.readLine
        End While
    End Sub

The running program is shown in Figure 15-5.

Two list boxes loaded using two instances of the ListAdapter

Figure 15-5. Two list boxes loaded using two instances of the ListAdapter

TreeView Adapters for VB.NET

The TreeView class in VB7 is only slightly different from that in VB6. For each node you want to create, you create an instance of the TreeNode class and add the root TreeNode collection to another node. In our example version using the TreeView, we'll add the swimmer's name to the root node collection and the swimmer's time as a subsidiary node. Here is the entire TreeAdapter class. Note that we only need implement the addText method.

Public Class TreeAdapter
    'An adapter to use TreeView
    'instead of list boxes
    Private Tree As TreeView    'instance of tree

    Public Sub New(ByVal tr As TreeView)
        Tree = tr
    End Sub
    '-------------
    Public Sub addText(ByVal sw As Swimmer)
        Dim scnt As String
        Dim nod As TreeNode
        'add a root node
        nod = Tree.Nodes.add(sw.getName)
        'add a child node to it
        nod.Nodes.add(sw.getTime.toString)
        Tree.expandAll()
    End Sub

End Class

The TreeDemo program is shown in Figure 15-6.

The same swimmer selection program using a TreeView adapter

Figure 15-6. The same swimmer selection program using a TreeView adapter

Adapting a DataGrid

The VB7 DataGrid control is considerably more elaborate than the MSFlexGrid control in VB6. It can be bound to a database or to an in-memory data array. To use the DataGrid without a database, you create an instance of the DataTable class and add DataColumns to it. DataColumns are by default of String type, but you can define them to be of any type when you create them. Here is the general outline of how you create a DataGrid using a DataTable.

dTable = New DataTable("Kids")
dTable.MinimumCapacity = 100
dTable.CaseSensitive = False
        Dim column As DataColumn
        column = New DataColumn("Frname", _
              System.Type.GetType("System.String"))

        dTable.Columns.Add(column)
        column = New DataColumn("Lname", _
              System.Type.GetType("System.String"))

        dTable.Columns.Add(column)
        column = New DataColumn("Age", _
              System.Type.GetType("System.Int16"))

        dTable.Columns.Add(column)

        DGrid.DataSource = dTable
        DGrid.CaptionVisible = False    'no caption
        DGrid.RowHeadersVisible = False 'no row headers
        DGrid.EndInit()

To add text to the DataTable, you ask the table for a row object and then set the elements of the row object to the data for that row. If the types are all String, then you copy the strings, but if one of the columns is of a different type, such as the integer age column here, you must be sure to use that type in setting that column's data.

The complete GridAdapter class fills in each row in this fashion.

Public Class GridAdapter
    Private dtable As DataTable
    Private Dgrid As DataGrid
    '-----
    Public Sub New(ByVal grid As DataGrid)
        dtable = CType(grid.DataSource, DataTable)
        dgrid = grid
    End Sub
    '-----
    Public Sub addText(ByVal sw As Swimmer)
        Dim scnt As String
        Dim row As DataRow

        row = dtable.NewRow
        row("Frname") = sw.getFirstName
        row(1) = sw.getLastName
        row(2) = sw.getAge  'This one is an integer
        dtable.Rows.Add(row)
        dtable.AcceptChanges()
    End Sub
End Class

Note that you can refer to each column either by numeric position or by name. The running program is shown in Figure 15-7.

The GridAdapter program

Figure 15-7. The GridAdapter program

The Class Adapter

In the class adapter approach, we derive a new class from Listbox (or the grid or tree control) and add the desired methods to it. This is possible in VB7 but not in earlier versions of Visual Basic. In the class adapter example on the CD-ROM, we create a new class called OurList which is derived from the Listbox class and which implements the following interface:

Public Interface ListAdapter
    'Interface Adapter for ListBox emulating some of
    'the methods of the VB6 list box.
    Sub addItem(ByVal s As String)
    Function ListIndex() As Integer
    Sub addText(ByVal sw As Swimmer)
End Interface

The class diagram is shown in Figure 15-8. The remaining code is much the same as in the object adapter version.

The class adapter approach to the list adapter

Figure 15-8. The class adapter approach to the list adapter

There are also some differences between the class and the object adapter approaches, although they are less significant than in C++.

The class adapter

  • Won't work when we want to adapt a class and all of its subclasses, since you define the class it derives from when you create it.

  • Lets the adapter change some of the adapted class's methods but still allows the others to be used unchanged.

An object adapter

  • Could allow subclasses to be adapted by simply passing them in as part of a constructor.

  • Requires that you specifically bring any of the adapted object's methods to the surface that you wish to make available.

Two-Way Adapters

The two-way adapter is a clever concept that allows an object to be viewed by different classes as being either of type ListBox or type MSFlexGrid. This is most easily carried out using a class adapter, since all of the methods of the base class are automatically available to the derived class. However, this can only work if you do not override any of the base class's methods with any that behave differently.

Object versus Class Adapters in VB.NET

The VB.NET List, Tree, and Grid adapters we previously illustrated are all object adapters. That is, they are all classes that contain the visual component we are adapting. However, it is equally easy to write a List or Tree Class adapter that is derived from the base class and contains the new addText method.

In the case of the DataGrid, this is probably not a good idea because we would have to create instances of DataTables and Columns inside the DataGrid class, which makes one large complex class with too much knowledge of how other classes work.

Pluggable Adapters

A pluggable adapter is one that adapts dynamically to one of several classes. Of course, the adapter can only adapt to classes it can recognize, and usually the adapter decides which class it is adapting based on differing constructors or setParameter methods.

Adapters in VB

In a broad sense, there are already a number of adapters built into the VB7 language to allow for compatibility with VB6. These wrap new functions in the API of the older ones in much the same way we did for the Listbox previously.

Thought Question

Thought Question

How would you go about writing a class adapter to make the Grid look like a two-column list box?

Programs on the CD-ROM

AdapterTreeAdapter The VB6 Tree adapter
AdapterVBNetLstAdapter VB7 List adapter
AdapterVBNetGrdAdapter VB7 Grid adapter
AdapterVBNetTreAdapter VB7 Treeview adapter
AdapterVBNetClassAdapter VB7 Class-based list adapter
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset