Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Applying Design Patterns to PowerBuilder


JUN96: Applying Design Patterns to PowerBuilder

Applying Design Patterns to PowerBuilder

The Observer pattern provides a window communication mechanism

Mark Nielsen and Nick Abdo

Mark and Nick are members of the research and development staff at MetaSolv Software in Dallas, Texas. Mark can be reached at [email protected]; Nick, at [email protected].


As the applications being developed with PowerBuilder become increasingly complex, PowerBuilder developers often struggle to apply sound design techniques to their applications. With support for most object-oriented programming features, PowerBuilder provides a great environment for implementing many of the recently published object-oriented design patterns. Like many other programming languages however, PowerBuilder allows you to break all the rules of good object-oriented design. Design patterns help to provide a road map to achieving greater reusability and maintainability in applications. Many of us know there are usually more questions than answers when it comes to designing software systems, and anything describing solutions tends to get our attention. While design patterns have the potential to help PowerBuilder developers, most examples and related information on patterns are presented in languages other than PowerBuilder. This leaves the translation into PowerBuilder up to the developer and can limit the exposure of patterns to the PowerBuilder community.

In this article, we'll examine how common design patterns can help PowerBuilder developers make good design decisions. In particular, we'll apply one of the patterns to design a window communication mechanism. We'll then implement this design in a simple PowerBuilder application. Once you've seen one of the patterns implemented in PowerBuilder, you should be able to find other design patterns useful as well.

The Problem

Most applications developed using PowerBuilder contain multiple windows that display different views of the database. In many instances, multiple windows are displayed at the same time, reflecting different views of the same data. A common dilemma facing many PowerBuilder developers is deciding how and when information will be passed between windows to keep the information in the windows synchronized.

For example, Figure 1 shows a simple inventory application containing three windows. The Item Picture and Item Chart windows display different graphical representations of the item selected on the Inventory List window.

When you select an item from the Inventory List, the Item Picture and Item Chart windows' contents must change to reflect the selected inventory item. Decisions must be made about how the windows talk to one another. To synchronize displayed windows, it is common for PowerBuilder developers to simply add the necessary code to the Inventory List window to directly reference the other windows and their controls.

While this approach may solve the synchronization problem, it introduces others. First, because users may choose to open the windows in any order (or not open them at all), the Inventory List window becomes burdened with not only having to keep track of which specific windows are depending on it, but also what those windows are doing with its information.

As this type of tightly coupled design is spread throughout the system, maintenance of the application becomes difficult. Developers working on the display windows may not be aware that other windows are making direct references to its controls. As new features are added to the system, old features can begin to break. Also, if a new display window is needed, the other windows need to be updated to handle its synchronization. This tight coupling also has a negative effect on reuse. The Inventory List window would not function well in another context without modifications.

The Solution

The desired design would allow enhancements and additions to the windows without changing existing windows in the system. To achieve these goals, windows must be loosely coupled--that is, they must minimize direct references to one another. This is where design patterns are extremely useful. In their book Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995) Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides provide a catalog of general-purpose object-oriented design patterns. These design patterns can be used to solve many common object-oriented design problems. In reviewing the pattern catalog, we discovered a pattern referred to as the "Observer." This pattern, which was also presented in the article "Observations on Observer," by Erich Gamma and Richard Helm (Dr. Dobb's Sourcebook, September/October 1995), describes a register-and-notify mechanism that provides indirect communication among objects.

The Observer Pattern

According to Design Patterns, the Observer pattern "Define[s] a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically." In other words, the Observer pattern is designed so that one or more independent objects (Observers) watch or observe another object (the Subject) and perform some action based on changes in, or actions taken by, the subject.

The key to the Observer pattern is the registration and notification concept. An object that needs to observe another object registers its interest with that object. When the observed object is subsequently modified, it sends a message to each of its registered objects notifying them that a change has occurred. Each notified object then queries the subject object for the modification and performs the appropriate action to synchronize itself with the new state of the subject.

In Figure 2, a class diagram depicts the structure of the Observer pattern using OMT notation. As you can see, the Observer pattern consists of four classes:

  • The Subject class, which contains operations to Attach() or Register observers, to Detach() or Unregister observers, and to Notify() observers to update themselves by means of the Observer class' Update() operation.
  • The Observer class, which contains a default Update() operation defining the update interface for the Subject class.
  • The ConcreteSubject class, which is inherited from the Subject class and contains attributes (represented by subjectState) to hold the current state of the object and operations (represented by GetState()) to return the object's state to any interested object.
  • The ConcreteObserver class, which is inherited from the Observer class and contains attributes (represented by observerState) to hold the current state of the object and an Update() operation that overrides the default Update() operation from the Observer class. The ConcreteObserver's Update() operation gets the state of the ConcreteSubject class (the observed object) with which it is registered.
In the basic Observer pattern structure, a Subject may reference zero-to-many Observers, while a ConcreteObserver may only reference a single ConcreteSubject.

At run time, Observers register with a Subject, indicating their interest in the Subject. As the Subject changes, it invokes its Notify() operation (defined in the superclass), which invokes the Update() operation on all registered Observers. Polymorphism is used here, allowing the Subject to only need to know about an abstract Observer class. This is the key to achieving the loose coupling of windows. Upon receiving the Update() message, each Observer interrogates the Subject for its new state, and then, maintaining encapsulation, updates itself accordingly.

PowerBuilder Implementation

Since many PowerBuilder applications are built with some type of class library or framework, we'll assume all our windows are inherited from a common ancestor window. This being the case, we can implement the Subject and Observer classes within this common ancestor window, which we'll call w_master. By implementing the pattern this way, all windows inherited from w_master can act as a subject, an observer, or both. The result is reflected in Figure 3. Referring to Figure 2, the association between the Subject class and the Observer class can be implemented as an instance variable in w_master, declared as an array of itself. This variable (iw_observers[]) will be used to hold references to each registered object. The operations of both the Subject and Observer classes are implemented as window functions of w_ master. The f_Attach() and f_Detach() functions are responsible for maintaining the array of registered objects, while f_ Notify() is responsible for looping through the array and invoking the Update() operation on each element. The Update() operations on w_master and all observing windows were renamed to f_Refresh() to avoid confusion with the Update() function of the datawindow. In the concrete windows, the associations between the ConcreteObservers and the ConcreteSubject are implemented as instance variables in each of the ConcreteObservers: w_picture, w_piechart, and w_bargraph (see Listing One).

The menu is used to open the observing windows and uses the f_SetSubject() function to set their subject to w_inventory (see Listing Six). Within f_SetSubject, the iw_ subject variable is set to hold a reference to w_inventory, then the window registers its interest in w_inventory by calling the f_Attach() function, using itself as an argument (see Listings Three, Four, and Five).

The notification process needs to begin when the row focus changes in the w_ inventory window's datawindow. The script in this event simply calls w_inventory's f_Notify() function (inherited from w_ master).

Finally, each observing window implements its own f_Refresh() window function to process the change. This function's purpose is to retrieve the current state of w_inventory (for instance, what are the quantity values for the selected item?), then refresh the window's state to reflect that of w_inventory. Encapsulation is achieved by the creation and use of the access functions provided by w_inventory (see Listing Two).

As a test of the design's loose coupling, the w_bargraph window was actually added as an afterthought. No existing code was changed. The w_bargraph window was created by inheriting from w_master. The appropriate script was added to have it register with the subject and its f_Refresh() function was scripted to reflect the subject's state.

Other Uses and Implementations

Several implementation variations are worth mentioning. The observing windows could also perform updates to the subject window. In this case, the subject would again simply call its f_Notify() function to alert all registered observers of any change. You also have options for establishing the reference to the subject from the observing windows. We chose to implement the f_SetSubject() function, but this could be performed in other ways, depending on whether it's appropriate for the observing windows to initiate relationships to the subject. The detaching of observers from the subject is also specific to your application. We chose to close all observers when the subject closes.

Conclusion

Design patterns can take much of the guesswork out of making design decisions. They can help validate your design ideas and can provide a starting point for weighing trade-offs and contradictions presented during object-oriented system development. Putting design patterns to work in PowerBuilder involves mapping the generic object-oriented constructs present in the patterns to PowerBuilder-specific constructs. In this example, we implemented a variation of the Observer pattern in PowerBuilder (available electronically; see "Availability," page 3). In making the translation, the pattern was modified slightly to fit within common PowerBuilder development practices. The important thing to remember while working with patterns is that they only present a generic solution to a design problem. In many cases, the actual implementation varies significantly from the basic pattern. If you don't take everything literally, and experiment a little, design patterns can be invaluable when developing PowerBuilder applications.

References

Gamma, E., R. Helm, R. Johnson, J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software, Reading, MA: Addison-Wesley, 1995.

Coplien, J. and D. Schmidt, eds. Pattern Languages of Program Design, Reading, MA: Addison-Wesley, 1995.

For More Information:

Powersoft

561 Virginia Road

Concord, MA 01742-2732

508-287-1500

http://www.powersoft.com

MetaSolv Software

14900 Landmark, Suite 530

Dallas, TX 75240

214-239-0623

http://www.metasolv.com

Figure 1: Application requiring communication between windows.

Figure 2: Basic structure of the Observer pattern.

Figure 3: Class diagram depicting implementation of the Observer pattern.

Listing One

w_master -- Ancestor window for subjects and observers

instance variables
// holds references to attached observers
w_master iw_observers[]

window functions:
public function boolean f_attach (w_master aw_observer)
public function boolean f_detach (w_master aw_observer)
public function boolean f_notify ()
public function boolean f_refresh ()

public function boolean f_attach (w_master aw_observer);
// purpose: attach the passed object as an observer
// add the passed object to next slot in the array
iw_observers[upperbound(iw_observers) + 1] = aw_observer
// tell new observer to refresh itself based on current state of subject
aw_observer.f_refresh()
return true

public function boolean f_detach (w_master aw_observer);
// purpose: detach the passed object, removing it from list of known observers
// declare tmp array to hold observers still attached
w_master lw_tmp_observers[]
integer li_total_observers, li_index, li_tmp_index = 1
// get total number of observers
li_total_observers = upperbound(iw_observers)
// loop through observers and tranfer them to the tmp
// array except for the passed observer
for li_index = 1 to li_total_observers
    if iw_observers[li_index] <> aw_observer then
        lw_tmp_observers[li_tmp_index] = iw_observers[li_index]
        li_tmp_index = li_tmp_index + 1

   end if
end for
iw_observers = lw_tmp_observers
return true

public function boolean f_notify ();
// purpose: call the f_refresh() function on all attached observers. 
//   this function should be called whenever changes are made to this subject
integer li_index, li_totalobservers
// get the total number of attached observers
li_totalobservers = upperbound(iw_observers)
// tell all attached observers to refresh
for li_index = 1 to li_totalobservers
    iw_observers[li_index].f_refresh()
end for
return true

public function boolean f_refresh ();
// purpose: resync to new state of the subject this function should be 
//   implemented in descendants to refresh based on changes to subject object
return true

Listing Two

w_inventory -- Inventory list window as the subject
controls:
dw_items from datawindow within w_inventory

window functions:
public function string f_getitemname ()
public function long f_getonhand ()
public function long f_getbackordered ()
public function long f_getonorder ()
public function long f_getreserved ()

public function string f_getitemname ();
// purpose: return the name of the selected item
// returns: string - name of selected item
return dw_items.getitemstring(dw_items.getrow(),"itemname")

public function long f_getonhand ();
// purpose: return onhand value of the selected item
// returns: long - onhand value
return dw_items.getitemnumber(dw_items.getrow(),"onhand")

public function long f_getbackordered ();
// purpose: return backorder value of the selected item
// returns: long - backorder value
return dw_items.getitemnumber(dw_items.getrow(),"backordered")


public function long f_getonorder ();
// purpose: return onorder value of the selected item
// returns: long - onorder value
return dw_items.getitemnumber(dw_items.getrow(),"onorder")

public function long f_getreserved ();
// purpose: return reserved value of the selected item
// returns: long - reserved value
return dw_items.getitemnumber(dw_items.getrow(),"reserved")

window events:
on open
// initialize the datawindow to the first row
dw_items.setrow(1)
dw_items.postevent(rowfocuschanged!)
end on

on close
// close all observer windows
do while upperbound(iw_observers) > 0
    close(iw_observers[1])
loop
end on

control events:
on dw_items.rowfocuschanged
// purpose: highlight the selected row and notify observers
// highlight the chosen row 
this.selectrow(0, false)
this.selectrow(getrow(), true)
// tell the window to notify all observers of the change
parent.f_notify()
end on

Listing Three

w_picture -- Item picture window as an observer 
controls:
p_item from picture within w_picture
instance variables:
private:
// holds a reference to the subject object
w_inventory iw_subject
window functions:
public function boolean f_refresh ()
public function boolean f_setsubject (ref w_master aw_subject)

public function boolean f_refresh ();
// purpose: update ourself to reflect the current state of the subject
// get the item name from the subject and set the bitmap on picture control.
if isvalid(iw_subject) then
    p_item.picturename = iw_subject.f_getitemname() + ".bmp"
end if
return true

public function boolean f_setsubject (ref w_master aw_subject);
// purpose: set the subject object
// set the instance variable used to hold the subject
iw_subject = aw_subject
// attach ourself with the subject
if isvalid(iw_subject) then
    iw_subject.f_attach(this)
end if
return true

window events:
on close
// purpose: detach with the subject
// detach with the subject
if isvalid(iw_subject) then

    iw_subject.f_detach(this)
end if
end on

Listing Four

w_piechart -- Item pie chart window as an observer 
controls:
dw_pie from datawindow within w_piechart
instance variables
private:
// holds a reference to the subject object
w_inventory iw_subject
window functions:
public function boolean f_refresh ()
public function boolean f_setsubject (ref w_master aw_subject)

public function boolean f_refresh ();
// purpose: update ourself to reflect the current state of the subject
// return if not attached to a subject
if not isvalid(iw_subject) then
    return false
end if

// set the piechart data elements based on the subject.
dw_pie.setitem(1, "quantity", iw_subject.f_getonhand())
dw_pie.setitem(2, "quantity", iw_subject.f_getbackordered())

dw_pie.setitem(3, "quantity", iw_subject.f_getonorder())
dw_pie.setitem(4, "quantity", iw_subject.f_getreserved())
return true

public function boolean f_setsubject (ref w_master aw_subject);
// purpose: set the reference to the subject and attach
// set the instance variable used to hold the subject
iw_subject = aw_subject
// attach ourself with the subject
if isvalid(iw_subject) then
    iw_subject.f_attach(this)
end if
return true

window events:
on close
// detach with the subject
if isvalid(iw_subject) then
    iw_subject.f_detach(this)
end if
end on

Listing Five

w_bargraph -- Item Bar chart window as an observer
controls:
dw_bar from datawindow within w_bargraph

instance variables:
private:
// holds a reference to the subject object
w_inventory iw_subject

window functions:
public function boolean f_setsubject (ref w_master aw_subject)
public function boolean f_refresh ()

public function boolean f_setsubject (ref w_master aw_subject);
// purpose: set the reference to the subject and attach
// set the instance variable used to hold the subject
iw_subject = aw_subject

// attach ourself with the subject
if isvalid(iw_subject) then
    iw_subject.f_attach(this)
end if
return true

public function boolean f_refresh ();
// purpose: update ourself to reflect the current state of the subject
// return if not attached to a subject

if not isvalid(iw_subject) then
    return false
end if
// set the barchart data elements based on the subject
dw_bar.setitem(1, "quantity", iw_subject.f_getonhand())
dw_bar.setitem(2, "quantity", iw_subject.f_getbackordered())
dw_bar.setitem(3, "quantity", iw_subject.f_getonorder())
dw_bar.setitem(4, "quantity", iw_subject.f_getreserved())
return true

window events:
on close
// detach with the subject
if isvalid(iw_subject) then
    iw_subject.f_detach(this)
end if
end on

Listing Six

m_frame -- Main Frame menu
type m_inventory from menu within m_view
type m_picture from menu within m_view
type m_piechart from menu within m_view
type m_barchart from menu within m_view

m_inventory from menu within m_view
on clicked;
// purpose: open w_inventory window which will become subject in this example
// open inventory window as subject
opensheet(w_inventory, w_Frame, 0, Original!)

m_picture from menu within m_view
on clicked
// purpose: open w_picture window as an observer and set subject to w_inventory
// open the w_picture
OpenSheet(w_picture, w_frame, 0, Original!)
// set its subject to w_inventory
w_picture.f_setsubject(w_inventory)
end on

m_piechart from menu within m_view
on clicked
// purpose: open w_piechart window as observer and set subject to w_inventory
// open the w_piechart window
OpenSheet(w_piechart, w_frame, 0, Original!)
// set its subject to w_inventory
w_piechart.f_setsubject(w_inventory)
end on


m_barchart from menu within m_view
on clicked
// purpose: open w_bargraph window as observer and set subject to w_inventory
// open the w_bargraph window
OpenSheet(w_bargraph, w_frame, 0, Original!)
// set its subject to w_inventory
w_bargraph.f_setsubject(w_inventory)
end on


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.