Tips_tutorials   >   Studio105   >   Studio 105 (All Contents)

Introduction

Welcome to the Studio 105 tutorial.

This tutorial is provided to StudioTips Members only. If you are not currently a StudioTips Member please go to www.studiotips.net and click the link provided for becoming a StudioTips Member. The cost of membership is minimal and helps to cover the time and expense of creating these tutorials and maintaining the studiotips.net website.

This tutorial delves into the advances object-oriented programming topic of observers based on the Observer Design Pattern.

This tutorial continues from where we left off in the Studio 104 tutorial. In this tutorial we will:

  1. Learn about loose coupling vs. tight coupling.
  2. Add an observers list and respective methods to the wHeadedList window class.
  3. Modify the parent window to attach itself as an observer of headed list events in the countries list subwindow.
  4. Create an oObservers object class which can be reused to easily create and manage observers for different situations.
This tutorial builds on the Studio 104 library. You must complete Studio 104 prior to starting this tutorial.

Loose vs. Tight Coupling

In the object-oriented programming world you will run across the terms loose coupling vs. tight coupling.

Loose coupling is the best design for good object-oriented programming.

In the Studio 104 tutorial the wHeadedList subwindow was tightly coupled to the wCountryStateprovContainer parent window.

The parent window was required to register with the child window using $setParentRef and to have an $event_HeadedListObj method if it wanted to received forwarded events from the subwindow. This is tight coupling.

A problem that became evident with this tight coupling was that a Switch/Case had to be put into the $event_HeadedListObj parent window method to figure out which subwindow the event message was coming from. Was it the countries list, or was it the states/provs list?

If you see Switch/Case or If/Else if being used in a method it may be a red flag that tight coupling is being used.

Loose coupling can be attained in the above situation by implementing observers based on the Observer Design Pattern.

Observer Design Pattern

The Observer Design Pattern works much like an email mailing list.

A person wanting to receive messages from a particular list adds themself as a subscriber to the mailing list. When they subscribe to the list, they provide their email address. A copy of each email posted to the list is then automatically sent to the email address specified by the subscriber.

An enhancement to the above mailing list illustration would be to allow subscribers to specify a category of messages they wish to receive.

Applying the above mailing list illustration to a parent/child window relationship in Omnis Studio:

  1. The child window maintains the mailing list. We call this the observers list.
  2. The parent window subscribes to the mailing list. The parent window is an observer.
  3. The parent window's email address is a reference to its $cinst plus a public call back method name which it specifies.
  4. The categories of messages the parent wishes to receive are the event codes which it specifies.
The above should will become clearer to you as we add the observer design pattern to the parent/child windows in this tutorial.

Add Observers List and Methods

We first need to prepare the observers list variable and methods in the wHeadedList window class.

  1. F2 Browser > double-click wHeadedList
  2. Add the variable iObserversList - Type List to the instance variables.
  3. Add the class method $attachHeadedListObserver with the following code:

    ; Define the observers list if it hasn't yet been defined.
    If iObserversList.$colcount=0
       Do iObserversList.$cols.$add('observerid',kCharacter,kSimplechar,10000)
       Do iObserversList.$cols.$add('observerref',kItemref)
       Do iObserversList.$cols.$add('callbackmethodname',kCharacter,kSimplechar,100)
       Do iObserversList.$cols.$add('eventcode',kInteger,kLongint)
    End If

    ; Create a unique ID for the observer.
    Calculate ObserverID as con(prObserver().$class().$name,'__',prObserver().$name,'__',prObserver.$ident)

    ; Parse the pEventCodes CSV string, adding a line to the observers list for each event code specified.
    While len(pEventCodesCSV)
       Calculate EventCode as strtok('pEventCodesCSV',',')
       
       ; Check to make sure the observer/callback/eventcode hasn't already been added to avoid double messages.
       If not(iObserversList.$search($ref.observer=ObserverID&$ref.callbackmethodname=pCallbackMethodName&$ref.eventcode=eval(EventCode),1,0,0,0))
          Do iObserversList.$add(ObserverID,prObserver,pCallbackMethodName,eval(EventCode))
       End If
    End For

    Quit method kTrue

  4. Add the class method $notifyHeadedListObservers with the following code:

    Calculate FlagOK as kTrue ;; Preset flag to true.

    ; Loop through the observers list.
    For iObserversList.$line from 1 to iObserversList.$linecount step 1
       
       ; Check to make sure the event code matches the event code specified by the observer.
       If iObserversList.eventcode=pkEventCode
          
          Set reference rObserver to iObserversList.observerref
          
          ; Test to make sure the observer has a recipient methdo.
          If not(rObserver.[iObserversList.callbackmethodname].$cando)
             
             ; Log an error. The observer is missing the specified recipient method.
             Calculate Mssg as con("The headed list observer '",rObserver.$cinst().$class().$name,"' is missing the call back method '",iObserversList.callbackmethodname,"'.")
             Calculate Dtls as ''
             Do errhndlr.$logError($cmethod,Mssg,Dtls)
             Calculate FlagOK as kFalse
             Break to end of loop
             
          Else
             
             ; Send a message to the observer.
             Do rObserver.[iObserversList.callbackmethodname](prcobj,pkEventCode) Returns FlagOK
             If not(FlagOK)
                Break to end of loop
             End If
          End If
          
       End If
       
    End For
    Quit method FlagOK

  5. Modify the $event method of the HeadedListObj as follows:

    ; Notify the headed list observers.
    Do $cinst.$notifyHeadedListObservers($cobj,pEventCode) Returns FlagOK
    If not(FlagOK)
       Do errhndlr.$promptonceLastError()
    End If
    Quit method FlagOK

Attach Parent Window as an Observer

Next we need to modify wCountryStateprovContainer so that it attaches itself as an observer of the countries headed list subwindow.

  1. F2 Browser > double-click wCountryStateprovContainer
  2. Select the constructSubWin_Countries method and add the following parameters.

    prcobj - Type - Item Reference
    pkEventCode - Type - Number - Subtype - Long Integer
  3. Modify the constructSubWin_Countries method code as follows:

    ; Attach the parent window as an observer of the headed list.
    Calculate CallBackMethod as '$event_HeadedListCountries'
    Do irswCountries.$attachHeadedListObserver($cinst,CallBackMethod,evClick) Returns FlagOK
    If FlagOK
       
       ; Set the reference to the data list variable.
       Do irswCountries.$setDataListRef(iCountriesList) Returns FlagOK
       If FlagOK
          
          ; Get a reference to the list object.
          Do irswCountries.$:ListObjRef() Returns rListObj
          
          ; Set the list object properties.
          Do rListObj.$name.$assign('CountriesList')
          Do rListObj.$colcount.$assign(1)
          Do rListObj.$columnnames.$assign('Country')
          Do rListObj.$calculation.$assign('irDataList.CountryName')
          
       End If
    End If
    Quit method FlagOK



    This specifies that we want to be notified whenever an evClick event occurs in the headed list. Notifications are to be sent to the $event_HeadedListCountries call back method.
  4. Add the class method $event_HeadedListCountries.
  5. Add the following parameters to the $event_HeadedListCountries method.

    prcobj - Type - Item Reference
    pkEventCode - Type - Number - Subtype - Long Integer
  6. Add the following code to the $event_HeadedListCountries method:

    Calculate FlagOK as kTrue ;; Preset flag to true
    If pkEventCode=evClick
       Do method buildStateprovsListForCurrCountry Returns FlagOK
    End If
    Quit method FlagOK



    This is the call back (or recipient) method which we specified when we attached the parent window as an observer of the headed list evClick events.
  7. We can comment out the $setParentRef line of code in constructSubWin_Stateprovs and we can remove the old $event_HeadedList method as they are no longer needed.

    Loose coupling using the observer design pattern has allowed the parent window to specify the call back method thereby negating the need to receive and handle event messages from the Stateprovs subwindow.

Testing the Observers

To test the observer design pattern which we have created:

  1. Contacts menu > select Countries, States/Provs Browser
  2. Click on various countries, the states/provs list should be rebuilt each time to reflect the states/provs linked to the selected country.

Create Observers Object Class

We can move the observers design pattern variable and methods into an object class which can then be reused over an over in different classes, and can even be instantiated multiple times within the same class instance to create more than one observers list.

  1. F2 Browser > click New Class > click Object
  2. Name the object class oObservers
  3. Double-click oObservers to go to the methods.
  4. Copy the $attachHeadedListObserver method from wHeadedList to oObservers.
  5. Rename the copied method to $attachObserver.
  6. Copy the $notifyHeadedListObservers method from wHeadedList to oObservers.
  7. Rename the copied method to $notifyObservers.
  8. An additional enhancement we can add to the oObservers object class is the ability for an observer to unsubcribe from the observers list. Add a method called $removeObserver to the class methods.
  9. Add a parameter variable to the $removeObserver method:

    prObserver - Type - Item Reference
  10. Add the following code to the $removeObserver method:

    ; Remove the observer from the observers list.
    Calculate ObserverID as con(prObserver().$class().$name,'__',prObserver().$name,'__',prObserver.$ident)
    Do iObserversList.$search($ref.observerid=ObserverID)
    Do iObserversList.$remove(kListDeleteSelected)
    Quit method kTrue

Implement Observers Object Class

We can now implement the oObservers object class in wHeadedList.

  1. F2 Browser > double-click wHeadedList
  2. Add a new instance variable:

    ioHeadedListObservers - Type Object Subtype oObserver

    This instantiates the oObservers object using the ivar ioHeadedListObservers. We could use another ivar to create another instance of oObservers to create another list of observers. For example I sometimes create an ioDataObservers ivar which notifies observers whenever the window writes changes to the database. The data observers receive the data list variable as a parameter with the notification message.
  3. Modify the $attachHeadedListObserver method as follows:

    ; Forward the message to the instance of oObservers.
    Do ioHeadedListObservers(prObserver,pCallbackMethodName,pEventCodesCSV) Returns FlagOK
    Quit method FlagOK

  4. Modify the $notifyHeadedListObservers method as follows:

    ; Forward the message to the instance of oObservers.
    Do ioHeadedListObservers.$notifyObservers(prcobj,pkEventCode) Returns FlagOK
    Quit method FlagOK

  5. Add a $removeHeadedListObserver method with code as follows:

    ; Forward the message to the instance of oObservers.
    Do ioHeadedListObservers.$removeObserver(prObserver)

As you can see, the oObservers object class makes it very easy to add an observers list to any window class or for that matter any class instance. I use the observers list when adding menus lines on-the-fly to a context menu instance. Each menu line can have a different observer and callback method.

Open the Countries, States/Provs Browser window and test the countries list to make sure the oObservers object class is working successfully.

Summary

Well that wraps up the Studio 105 tutorial.

I hope that this tutorial has helped you to:

  1. See how loose coupling is the best choice of object-oriented programming.
  2. Understand the Observer Design Pattern and how it can be used for loose coupling of class instances in Omnis Studio.

Go ahead and copy the oObservers object class into your own application and make use of it where ever it makes sense to do so.

If this tutorial has been helpful please send an emai to doug@vencor.ca. It's always encouraging to hear from developers who have benefited from these tutorials. Be sure to include any suggestions for improvements or additional topics you would like to see covered.

Visit www.studiotips.net to find out more and to become a StudioTips Member. Your support is greatly appreciated!

Happy coding!

Doug Kuyvenhoven
Vencor Software