Chapter 26. The Mediator Pattern

When a program is made up of a number of classes, the logic and computation is divided logically among these classes. However, as more of these isolated classes are developed in a program, the problem of communication between these classes becomes more complex. The more each class needs to know about the methods of another class, the more tangled the class structure can become. This makes the program harder to read and harder to maintain. Further, it can become difficult to change the program, since any change may affect code in several other classes. The Mediator pattern addresses this problem by promoting looser coupling between these classes. Mediators accomplish this by being the only class that has detailed knowledge of the methods of other classes. Classes inform the Mediator when changes occur, and the Mediator passes on the changes to any other classes that need to be informed.

An Example System

Let's consider a program that has several buttons, two list boxes, and a text entry field, as shown in Figure 26-1.

A simple program with two lists, two buttons, and a text field that will interact

Figure 26-1. A simple program with two lists, two buttons, and a text field that will interact

When the program starts, the Copy and Clear buttons are disabled.

  1. When you select one of the names in the left-hand list box, it is copied into the text field for editing, and the Copy button is enabled.

  2. When you click on Copy, that text is added to the right-hand list box, and the Clear button is enabled, as we see in Figure 26-2.

    When you select a name, the buttons are enabled, and when you click on Copy, the name is copied to the right list box.

    Figure 26-2. When you select a name, the buttons are enabled, and when you click on Copy, the name is copied to the right list box.

  3. If you click on the Clear button, the right-hand list box and the text field are cleared, the list box is deselected, and the two buttons are again disabled.

User interfaces such as this one are commonly used to select lists of people or products from longer lists. Further, they are usually even more complicated than this one, involving insert, delete, and undo operations as well.

Interactions between Controls

The interactions between the visual controls are pretty complex, even in this simple example. Each visual object needs to know about two or more others, leading to quite a tangled relationship diagram, as shown in Figure 26-3.

A tangled web of interactions between classes in the simple visual interface we presented in Figures 26-1 and 26-2

Figure 26-3. A tangled web of interactions between classes in the simple visual interface we presented in Figures 26-1 and 26-2

The Mediator pattern simplifies this system by being the only class that is aware of the other classes in the system. Each of the controls with which the Mediator communicates is called a Colleague. Each Colleague informs the Mediator when it has received a user event, and the Mediator decides which other classes should be informed of this event. This simpler interaction scheme is illustrated in Figure 26-4.

A Mediator class simplifies the interactions between classes.

Figure 26-4. A Mediator class simplifies the interactions between classes.

The advantage of the Mediator is clear: It is the only class that knows of the other classes and thus the only one that would need to be changed if one of the other classes changes or if other interface control classes are added.

Sample Code

Let's consider this program in detail and decide how each control is constructed. The main difference in writing a program using a Mediator class is that each class needs to be aware of the existence of the Mediator. You start by creating an instance of the Mediator and then pass the instance of the Mediator to each class in its init method.

Set med = New Mediator
med.registerKidList lsKids
med.registerPicked lsPicked
med.registerText txName
med.init

Our two buttons use accompanying Command pattern classes and register themselves with the Mediator during their initialization. Here is the CopyCommand class.

'Class CopyCommand
Implements Command
Private med As Mediator

Public Sub init(md As Mediator, cpBut As CommandButton)
 Set med = md
 med.registerCopy cpBut
End Sub

Private Sub Command_Execute()
 med.copyClicked
End Sub

The Clear button is exactly analogous.

The Kid name list is based on the one we used in the last two examples but expanded so that the data loading of the list takes place in the Mediator's init method.

Public Sub init()
 'init method for Mediator
 Dim kds As New Kids    'Kids class instabce
 Dim kd As Kid
 Dim iter As Iterator

kds.init App.Path & "50free.txt"  'read in file
 Set iter = kds.getIterator         'get iterator
While iter.hasMoreElements     'put names in list box
   Set kd = iter.nextElement
   kidList.AddItem kd.getFrname & " " & kd.getLname
 Wend
 clearClicked
End Sub

The text field is even simpler, since all we have to do is register it with the Mediator. The complete Form_Load event for the list box is shown here with all the registration and command classes.

Private Sub Form_Load()
 Set med = New Mediator         'create mediator
 Set cpyCmd = New CopyCommand   'copy command class
 cpyCmd.init med, btCopy

Set clrCmd = New ClearCommand  'clear command class
 clrCmd.init med, btClear

med.registerKidList lsKids     'register lists
 med.registerPicked lsPicked    'and text box
 med.registerText txName
 med.init                       'set all to beginning state
End Sub

The general point of all these classes is that each knows about the Mediator and tells the Mediator of its existence so the Mediator can send commands to it when appropriate.

The Mediator itself is very simple. It supports the Copy, Clear, and Select methods and has register methods for each of the controls.

Option Explicit
'Class Mediator
Private copyButton As CommandButton
Private clearButton As CommandButton
Private txtBox As TextBox
Private kidList As ListBox
Private pickedList As ListBox
'-----
Public Sub registerCopy(cpBut As CommandButton)
  Set copyButton = cpBut    'copy button
End Sub
'-----
Public Sub copyClicked()
 pickedList.AddItem txtBox.Text 'add text to picked list
 clearButton.Enabled = True     'enable clear button
 kidList.ListIndex = -1         'deselect list item
End Sub
'-----
Public Sub registerClear(clrBut As CommandButton)
  Set clearButton = clrBut      'clear button
End Sub
'-----
Public Sub clearClicked()
  txtBox.Text = ""              'clear text bos
  copyButton.Enabled = False    'disable buttons
  clearButton.Enabled = False
  pickedList.Clear              'clear picked list
  kidList.ListIndex = -1        'deselect list item
End Sub
'-----
Public Sub registerText(txt As TextBox)
 Set txtBox = txt               'text box
End Sub
'-----
Public Sub registerKidList(klist As ListBox)
 Set kidList = klist            'kid list
End Sub
'-----
Public Sub registerPicked(plist As ListBox)
  Set pickedList = plist        'picked list
End Sub
'-----
Public Sub listClicked()
 Dim i As Integer
 i = kidList.ListIndex
 If (i >= 0) Then
   txtBox.Text = kidList.Text
 End If
 copyButton.Enabled = True
End Sub
'-----
Public Sub init()
 'init method for Mediator
 Dim kds As New Kids    'Kids class instance
 Dim kd As Kid
 Dim iter As Iterator

kds.init App.Path & "50free.txt"  'read in file
 Set iter = kds.getIterator         'get iterator
 While iter.hasMoreElements         'put names in list box
   Set kd = iter.nextElement
   kidList.AddItem kd.getFrname & " " & kd.getLname
 Wend
 clearClicked                       'Set to initial state
End Sub

Initialization of the System

One further operation that is best delegated to the Mediator is the initialization of all the controls to the desired state. When we launch the program, each control must be in a known, default state, and since these states may change as the program evolves, we simply create an init method in the Mediator, which sets them all to the desired state. In this case, that state is the same as the one achieved by the Clear button, and we simply call that method this.

clearClicked       'Set to initial state

Mediators and Command Objects

The two buttons in this program use command objects. Just as we noted earlier, this makes processing of the button click events quite simple.

Private Sub btClear_Click()
 med.clearClicked
End Sub
'-----
Private Sub btCopy_Click()
 med.copyClicked
End Sub
'-----
Private Sub lsKids_Click()
 med.listClicked
End Sub

In either case, however, this represents the solution to one of the problems we noted in the Command pattern chapter: Each button needed knowledge of many of the other user interface classes in order to execute its command. Here, we delegate that knowledge to the Mediator, so the Command buttons do not need any knowledge of the methods of the other visual objects. The class diagram for this program is shown in Figure 26-5, illustrating both the Mediator pattern and the use of the Command pattern.

The interactions between the Command objects and the Mediator object

Figure 26-5. The interactions between the Command objects and the Mediator object

The Mediator in VB.Net

You can create a Mediator in much the same way in VB7, but you can take advantage of inheritance to make your work easier. The Copy and Clear buttons and the Kid name list can all be subclassed from the standard controls so that they support the Command interface and register themselves with the Mediator during the constructor. This makes the derived button classes very easy to write.

Public Class CpyButton
    Inherits System.Windows.Forms.Button
    Implements Command
    Private med As Mediator
    Public Sub setMediator(ByVal md As Mediator)
        med = md
        med.register(Me)
    End Sub
    '----
    'tell the Mediator we've been clicked
    Public Sub Execute() Implements Command.Execute
        med.copyClicked()
    End Sub
End Class

Further, since VB7 supports polymorphism, we can have a register method in the Mediator with different argument types for each control we want to register. These methods are shown here.

Public Overloads Sub register(ByVal cpb As CopyButton)
     cpbutton = cpb
End Sub
'-----
Public Overloads Sub register(ByVal clr _
              As ClearButton)
    clrbutton = clr
End Sub
'-----
Public Overloads Sub register(ByVal kd _
               As KidsListBox)
    klist = kd
End Sub
'-----
Public Overloads Sub register(ByVal pick As ListBox)
    pklist = pick
End Sub
'-----
Public Overloads Sub register(ByVal tx As TextBox)
    txkids = tx
End Sub

The remainder of the Mediator manipulates the various controls as before.

Public Sub kidPicked()
    'copy text from list to textbox
    txkids.Text = klist.Text
    'copy button enabled
    cpbutton.Enabled = True
End Sub
'-----
Public Sub copyClicked()
    'copy name to picked list
    pklist.Items.Add(txkids.Text)
    'clear button enabled
    clrbutton.Enabled = True
    klist.SelectedIndex = -1
End Sub
'-----
Public Sub clearClicked()
    'disable buttons and clear list
    cpbutton.Enabled = False
    clrbutton.Enabled = False
    pklist.Items.Clear()
End Sub
'-----

Initialization

When we create the controls, we start by creating an instance of the Mediator. Then as the buttons and list box controls are created, they can register themselves inside the constructor for each derived control.

Private Sub init()
        'called from New constructor
        Dim evh As EventHandler
        evh = New EventHandler(AddressOf CommandHandler)

        med = New Mediator()
        btCopy.setMediator(med)
        btClear.setMediator(med)
        kList.setMediator(med)
        'register remaining controls
        med.register(txName)
        med.register(lsPicked)
        med.init()      'initialize mediator

During initialization, the Mediator reads in the data file and puts the kid's names in the kidList list box. Note that the Kids class does the reading as before, using the vbFile class, and that the Mediator just provides the filename and loads the list once the file is read.

Public Sub init()
        'initializes the Mediator's objects
        Dim kd As Kid
        clearClicked()  'set to defaults
        'read in datafile and load list
        kds = New Kids(Application.StartupPath & "50free.txt")
        Dim iter As Iterator = kds.getIterator
        'Note we use the iterator here
        While (iter.hasMoreElements)
            kd = CType(iter.nextElement, Kid)
            klist.Items.Add(kd.getFrname + " " + kd.getLname)
        End While
     End Sub

Handling the Events for the New Controls

We create the new classes CopyButton, ClearButton, and KidListBox, and rather than declaring them as WithEvents, we simply add an event handler to each of them, which is the same simple handler in all three cases, and include this handler registration in the Forms init method called from its constructor.

AddHandler btCopy.Click, evh
AddHandler btClear.Click, evh
AddHandler kList.SelectedIndexChanged, evh

Now, the two buttons clicks and selecting a kid in the Listbox all call the CommandHandler. Since all three classes implement the Command interface, our command handler reduces to just two lines of code.

Public Sub CommandHandler(ByVal sender As Object, _
              ByVal e As System.EventArgs)
   Dim cmd As Command = CType(sender, Command)
   cmd.Execute()
End Sub

You can appreciate the simplification VB7 makes in using a Mediator by examining the UML diagram shown in Figure 26-6.

The UML diagram for the VB7 Mediator pattern

Figure 26-6. The UML diagram for the VB7 Mediator pattern

Consequences of the Mediator Pattern

  1. The Mediator pattern keeps classes from becoming entangled when actions in one class need to be reflected in the state of another class.

  2. Using a Mediator makes it easy to change a program's behavior. For many kinds of changes, you can merely change or subclass the Mediator, leaving the rest of the program unchanged.

  3. You can add new controls or other classes without changing anything except the Mediator.

  4. The Mediator solves the problem of each Command object needing to know too much about the objects and methods in the rest of a user interface.

  5. The Mediator can become a “god class,” having too much knowledge of the rest of the program. This can make it hard to change and maintain. Sometimes you can improve this situation by putting more of the function into the individual classes and less into the Mediator. Each object should carry out its own tasks, and the Mediator should only manage the inter action between objects.

  6. Each Mediator is a custom-written class that has methods for each Colleague to call and knows what methods each Colleague has available. This makes it difficult to reuse Mediator code in different projects. On the other hand, most Mediators are quite simple, and writing this code is far easier than managing the complex object interactions any other way.

Single Interface Mediators

The Mediator pattern described here acts as a kind of Observer pattern, observing changes in each of the Colleague elements, with each element having a custom interface to the Mediator. Another approach is to have a single interface to your Mediator and pass to that method various objects that tell the Mediator which operations to perform.

In this approach, we avoid registering the active components and create a single action method with different polymorphic arguments for each of the action elements.

Public Sub action(mv As MoveButton)
Public Sub action(clrButton As ClearButton)
Public Sub action(klst as KidList)

Thus, we need not register the action objects, such as the buttons and source list boxes, since we can pass them as part of generic action methods.

In the same fashion, you can have a single Colleague interface that each Colleague implements, and each Colleague then decides what operation it is to carry out.

Implementation Issues

Mediators are not limited to use in visual interface programs; however, it is their most common application. You can use them whenever you are faced with the problem of complex intercommunication between a number of objects.

Programs on the CD-ROM

Mediator VB6 Mediator
MediatorVBNet VB7 Mediator
..................Content has been hidden....................

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