Other techniques

PowerBuilder allows you to implement a wide variety of object-oriented techniques. This section discusses selected techniques and relates them to PowerBuilder.

Using function overloading

In function overloading, the descendent function (or an identically named function in the same object) has different arguments or argument datatypes. PowerBuilder determines which version of a function to execute, based on the arguments and argument datatypes specified in the function call:

Figure 2-6: Function overloading

The object u _ sort at the top of the hierarchy has a function uf _ sort ( ). Farther down, the object u _ sort _dw also has a function uf _ sort ( ) that overrides u _ sort . uf _ sort. In addition, u _ sort _ dw has the functions uf _ sort (integer) and uf _ sort (string) that overload u _ sort . uf _ sort. PowerBuilder executes the appropriate function based on the number of passed parameters and their datatypes.

NoteGlobal functions Global functions cannot be overloaded.

Dynamic versus static lookup

Dynamic lookup In certain situations, such as when insulating your application from cross-platform dependencies, you create separate descendent objects, each intended for a particular situation. Your application calls the platform-dependent functions dynamically:

Figure 2-7: Dynamic lookup

The ancestor u _ platform has the functions uf _ file _ read and uf _ file _ write. It has two descendants, u _ platform _ win, with the function uf _ get _ registry, and u _ platform _ unix, with the function uf _ get _ defaults. You instantiate the appropriate object at runtime, calling uf _ get _ registry and uf _ get _ defaults dynamically.

Instantiate the appropriate object at runtime, as shown in the following code example:

// This code works with both dynamic and
// static lookup.
// Assume these instance variables
u_platform iuo_platform
Environment ienv_env
...
GetEnvironment(ienv_env)
choose case ienv_env.ostype
   case windows!
      iuo_platform = CREATE u_platform_win
   case windowsnt!
      iuo_platform = CREATE u_platform_win
   case else
      iuo_platform = CREATE u_platform_unix
end choose

Although dynamic lookup provides flexibility, it also slows performance.

Static lookup To ensure fast performance, static lookup is a better option. However, PowerBuilder enables object access using the reference variable’s datatype (not the datatype specified in a CREATE statement).

Figure 2-8: Static lookup

Suppose you have the following code: u _ platform i u o _ platform ellipsis i u o _ platform =  CREATE u _ platform. Use the i u o _ platform reference variable to access u _ platform functions, user events, and instance variables. Suppose, however, that i u o _ platform = create u _ platform _ win32. Because i u o _ platform is defined as type u _ platform, you cannot use 32 bit descendent functions, such as uf _ get _ registry.

When using static lookup, you must define default implementations for functions in the ancestor. These ancestor functions return an error value (for example, -1) and are overridden in at least one of the descendent objects.

Figure 2-9: Ancestor functions overridden in descendent functions

The ancestor u _ platform has the functions uf _ get _ ini, uf _ get _ registry, uf _ file _ read, and uf _ file _ write. the descendent u _ platform _ win 16 has the function uf _ get _ ini, and the descendent u _ platform _ win 32 has the function uf _ get _ registry. The descendent functions perform all processing, and the ancestor functions  uf _ get _ ini and uf _ get _ registry return -1.

By defining default implementations for functions in the ancestor object, you get platform independence as well as the performance benefit of static lookup.

Using delegation

Delegation occurs when objects offload processing to other objects.

Aggregate relationship In an aggregate relationship (sometimes called a whole-part relationship), an object (called an owner object) associates itself with a service object designed specifically for that object type.

For example, you might create a service object that handles extended row selection in DataWindow objects. In this case, your DataWindow objects contain code in the Clicked event to call the row selection object.

StepsTo use objects in an aggregate relationship:

  1. Create a service object (u_sort_dw in this example).

  2. Create an instance variable (also called a reference variable) in the owner (a DataWindow control in this example):

    u_sort_dw iuo_sort
    
  3. Add code in the owner object to create the service object:

    iuo_sort = CREATE u_sort_dw
    
  4. Add code to the owner’s system events or user events to call service object events or functions. This example contains the code you might place in a ue_sort user event in the DataWindow control:

    IF IsValid(iuo_sort) THEN
       Return iuo_sort.uf_sort()
    ELSE
       Return -1
    END IF
    
  5. Add code to call the owner object’s user events. For example, you might create a CommandButton or Edit>Sort menu item that calls the ue_sort user event on the DataWindow control.

  6. Add code to the owner object’s Destructor event to destroy the service object:

    IF IsValid(iuo_sort) THEN
       DESTROY iuo_sort
    END IF
    

Associative relationship In an associative relationship, an object associates itself with a service to perform a specific type of processing.

For example, you might create a string-handling service that can be enabled by any of your application’s objects.

The steps you use to implement objects in an associative relationship are the same as for aggregate relationships.

Using user objects as structures

When you enable a user object’s AutoInstantiate property, PowerBuilder instantiates the user object along with the object, event, or function in which it is declared. You can also declare instance variables for a user object. By combining these two capabilities, you create user objects that function as structures. The advantages of creating this type of user object are that you can:

StepsTo create a user object to be used as a structure:

  1. Create the user object, defining instance variables only.

  2. Enable the user object’s AutoInstantiate property by checking AutoInstantiate on the General property page.

  3. Declare the user object as a variable in objects, functions, or events as appropriate.

    PowerBuilder creates the user object when the object, event, or function is created and destroys it when the object is destroyed or the event or function ends.

Subclassing DataStores

Many applications use a DataWindow visual user object instead of the standard DataWindow window control. This allows you to standardize error checking and other, application-specific DataWindow behavior. The u_dwstandard DataWindow visual user object found in the tutorial library TUTOR_PB.PBL provides an example of such an object.

Since DataStores function as nonvisual DataWindow controls, many of the same application and consistency requirements apply to DataStores as to DataWindow controls. Consider creating a DataStore standard class user object to implement error checking and application-specific behavior for DataStores.