pplications often need to propagate changes to data to synchronize the state of objects in an application, particularly in user-interface applications where multiple windows can reference the same data. In this type of application, changes to elements of the underlying information must be reflected in all the windows containing references to it.
Take, for example, an application that manages information about people. Multiple windows containing the name of a person might be open at once. If you change the person’s name in one window and save, you would expect the name to immediately change in all other windows as well. You can accomplish this functionality by leveraging a design pattern known as Publish/Subscribe. This design pattern is a variation of the Observer pattern (see Figure 1) described in Design Patterns, Elements of Reusable Object Oriented Software, a work many consider the original design patterns book. In the Observer pattern, an object (the Observer) registers to listen for events from another object (the Subject); the Observer is implicitly aware of the Subject.
The Publish/Subscribe (see Figure 2) variation of this pattern introduces the Event Channel, a layer of separation between the Subject and the Observer. This layer removes the binding between the Observer and the Subject and creates a loosely coupled relationship between the two.
The Event Channel can be described as a messaging center that routes events. The Publisher (know in the Observer pattern as the Subject) publishes events to the Event Channel. The Event Channel is responsible for disseminating the events to all of the Subscribers (know in the Observer pattern as the Observer). An application may contain one or more Event Channels, each disseminating different types of events to interested Subscribers. Also, whereas in Observer the events come only from a specific source, in Publish/Subscribe any object that is aware of the Event Channel may publish an event.
This architecture removes the dependency between the Observer and Subject, resulting in a more generic design for your application. Also, by requiring no direct relationship between the Publisher and the Subscriber, it increases the maintainability of your application.How the Publish/Subscribe Pattern Is Applied
To see how this pattern is applied, examine the sample application, EventApp. EventApp is an MDI environment in which the data in multiple MDI child windows is synchronized when changes are made (click here for the complete source code).
In your own apps, you will need to create a series of base classes for implementing the Publish/Subscribe pattern. The rest of the classes in the project will be derived from these classes (see Figure 3). The EventApp application has the following three base classes:
- clsEventChannel ? An abstract class for creating event channels.
- clsEvent ? An abstract class used for creating types of events. It exposes four properties:
- Name: The name of the event
- Value: The value of the event
- ExtraData: Other data that is associated to the events
- Origin: An optional reference identifying the publisher of the event
- frmSubscriber ? An abstract Windows Form used for creating forms that can receive events.
To make an application work, you also will need to create several concrete classes that inherit from the base classes. In the sample application, these classes are:
- clsDataEvents ? A concrete class that inherits from clsEventChannel. The application will use this class as an event channel to disseminate information about changes to the underlying data model.
- clsDataEvent ? A concrete class that inherits from clsEvent and is used to create an event type that represents changes to the underlying data. (To create an inherited form, you need to select “Add -> Inherited Windows Form” from the Solution Explorer context menu. The assistant will prompt you for the class from which to derive and then generate all the necessary code to make this work.)
- frmList ? A concrete class that is derived from the frmSubscriber and is a generic MDI child form used to display a list of data. In EventApp, this form is hard-coded to load a list of 10 items in a Listview control.
How EventApp Works
When EventApp starts up, frmMain (the MDI parent form) creates an instance of the data event channel, clsDataEvents, at the module level. The instance is kept in memory:
Private Sub frmMain_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Load mobjDataEventChannel = New clsDataEvents() Me.IsMdiContainer = True Me.Text = "Publish/Subscribe Event Sample" Me.WindowState = FormWindowState.MaximizedEnd Sub
When the MDI form is loaded, you can select “File->New List” from the menu to create a new instance of frmList:
Private Sub MenuItem2_Click(ByVal sender As _ System.Object, ByVal e As System.EventArgs) _ Handles MenuItem2.Click Dim objNewForm As frmList objNewForm = New frmList() mobjDataEventChannel.AttachSubscriber(objNewForm) objNewForm.MdiParent = Me objNewForm.Show()End Sub
When the child form is created, it is registered with the data event channel. During the registration process, the event channel will initialize the new subscriber, assigning it a token and passing it a reference to itself. It does this so that when a third party registers the class with an event channel (as opposed to registering itself with the channel), it will still be aware that the registration has occurred:
Public Overridable Sub Initialize(ByVal Token As _ String, ByVal EventChannel As clsEventChannel) mstrToken = Token mobjEventChannel = EventChannelEnd Sub
During the registration process, the subscriber is assigned a unique Token, which is used to identify the subscriber in the event channel. The event channel also uses this token internally to index the subscribers. Although the sample application uses the object’s Hash Code as the Token, I recommend using another approach to generate a unique identifier (such as generating a GUID) in your own apps.
Use the New List menu option to create three or four instances of frmList, then select an item in one of the lists. Click twice on the item to go into “label edit” mode. Type in a new name and press enter. The form creates an event describing the change to the data and sends it off through the event channel:
Private Sub lvwList_AfterLabelEdit(ByVal sender As _ Object,
ByVal e As _ System.Windows.Forms.LabelEditEventArgs) _Handles lvwList.AfterLabelEdit Dim objEvent As clsDataEvent If e.Label "" Then objEvent = New clsDataEvent() With objEvent .Name = "NameChanged" .Value = e.Label .ExtraData = lvwList.Items(e.Item).Tag .Origin = Me End With MyBase.EventChannel.PublishEvent(objEvent) End IfEnd Sub
Note that the form sending the event identifies itself as the originator; it sets the Origin property of the clsDataEvent class to itself. This prevents the sender from receiving the event that it published, which can shoot off redundant events. After all, the publisher of the event is well aware that the data has changed.
All the subscribers to the data event channel receive the event. In EventApp, every other instance of frmList receives the event and can react to it by changing the text of the appropriate item in the ListView:
Public Overrides Sub EventRaise( _ ByVal NewEvent As _ EventApp.clsEvent) Dim objListItem As ListViewItem Select Case NewEvent.Name Case "NameChanged" For Each objListItem In lvwList.Items If objListItem.Tag = NewEvent.ExtraData Then objListItem.Text = NewEvent.Value End If Next Case Else 'Handle other events End SelectEnd Sub
This type of event system gives you flexibility. You can have any object in an application publish events on a channel while only interested subscribers get notified of them. It also allows you to create multiple event channels that group functionally related event types.
You also can improve the implementation of the Event Channel class. You can create an Event Channel that systematically stacks the events and sends them out asynchronously when a set number is reached, either individually or all together. You could also implement an Event Channel that sends out the events over HTTP through a Web service or a messaging system such as MS Message Queue or IBM MQ Series.