Tech News Back Issues Issue: 010506

Object Classes and Instances: Helper Objects

By David Swain
Polymath Business Systems

In the last article we explored Function Objects. These are object instances designed to be called implicitly from within expressions. We pass them one or more values and they yield a return value based on the parameter(s) we supply. As the name indicates, we use these just like we would use any other function by calling their public methods implicitly from within expressions.

Helper Objects are a bit different in their purpose and operation. These are object instances that are called to perform some action or series of actions affecting the properties and/or methods of other instances, not usually to return a value. While we can certainly call them implicitly if we wish, Helper Objects are more likely to be invoked directly using the Do command.

But we must consider more factors in the design and implementation of Helper Objects if we want our use of them to go smoothly. Perhaps a brief analogy can help get this point of view across...

Little Known Facts ("You're a Good Man, Charlie Brown ", Clark Gesner)

When we design physical structures and systems, we often need to take into account a number of factors in choosing the right materials or characteristics for specific components. For example, some materials have great compressive strength while others have great tensile strength. Few materials have both. We use materials with high compressive strength (granite or concrete, for example) for components that will be subjected to high compressive forces (foundations, bridge footings, etc.). We use materials with high tensile strength (fiber rope, steel beams or cable) for components that will be subjected to high tensile stress (hoisting members or suspension bridges). A suspension bridge made of concrete alone will not even be able to bear its own weight, let alone vehicular traffic! Other problems would occur if the pilings of a bridge were made entirely of steel - but there would certainly be problems...

Other factors must be considered as well. The service environment in which a component must perform its function is also an important concern. Basic carbon steel in a marine environment won't last long because it corrodes readily when exposed to that combination of humidity and chemicals, but a different alloy might prove more corrosion resistant or a special coating might provide protection from the elements.

In electronic and electrical systems, we might need to be concerned about impedance compatibility of current-bearing or signal-bearing components in addition to physical and chemical attributes. And the needs of micro-electronic components are even more interesting. Alternatively, we might choose the materials for certain components (fuses) for their ability to predictably fail. The more sophisticated and sensitive the system, the more we must be concerned about the composition of its components.

High temperature, low temperature, high humidity, exposure to various chemicals - all these must at times be taken into account in the design process in a physical system, in addition to the working charactieristics of a component.

Gonna Build a Mountain ("Stop the World, I Want to Get Off ", Newley & Bricusse)

So why bring this up when discussing software? Virtual components are no less sensitive to their environments than are physical ones - there are just different factors for the designer to consider. We build our components to perform certain kinds of jobs. The more thought we put into both how and where these components will do those jobs, the better will be our results.

The creators of Omnis Studio made certain decisions about how various class types would be used. So we can do some things with Window Classes and other things with Report or Menu Classes. We can't swap a class of one type for a class of another type because they just don't work the same. (Windows and Remotes Forms cannot be interchanged - no matter how much we might want them to be - because they are designed for very different environments.) But Object Classes were intended to be a little more generalized in some ways. We can build Helper Objects to assist instances of any class type - and even to help instances of different class types to work better together.

Object Instances are deployed as Object Variables, so we can put them anywhere we can define a variable. But the outstanding characteristic of a variable (besides its data type) is its scope. This is a determining factor in our design decisions for a Helper Object.

There are two issues of scope to consider: The scope at which we intend to deploy the instances of our Object and the scope of its operation. These two factors often interact in the way they affect our design choices. Of course, if we remain unaware of these considerations, they only affect our effective use of our objects after they have been built - and often in a detrimental way.

Here is a general rule of thumb for designing and using Helper Objects: We must make sure we design our objects for the environment in which they are to be used and then use our objects in the environment for which they were designed. Does this sound overly simplistic? It's not always so easy to follow this advice - especially without a good plan. This is where many developers get into trouble using Objects.

Fiddle About, Fiddle About... ("Tommy", the Who)

Helper Objects are also somewhat "instrusive" for Object Oriented constructs. They are intended to stick their noses into the business of other elements of an application, but only for the sake of efficiency. Whether they do the jobs of surgeons or the jobs of sanitary workers, they must still poke around in and manipulate the properties and variables of other instances. That is their job. Generally they only need to access public items of other instances, but sometimes they need to deal with things that are not strictly public in the OO sense, but that Omnis Studio has left open to access from outside the instance (such as instance variables of the instances the Objects are helping).

To do this job most effectively, each method of a Helper Object Instance needs to know first where it is at and then where is the target of its process. If we both design and use each Helper Objects for a specific kind of environment, this is not much of an issue after those decisions have been made. But if we try to build our Helper Objects to be too general and then try to use them in a broad range of environments and scopes, we can occasionally run into difficulties. (The same goes for trying to use windows that are intended for use as both standalone instances and nested three levels deep as subwindows.) We then have to include additional resources (environment variables and parameters) to orient the method so it understands where it is working and on what. We will often find that Objects built too generally are impossible to use in some environments.

But we don't want to build Helper Objects that are too specific either. For example, we don't want to make Object Classes that work with only one Window Class, just for the sake of using Object Classes! Object Classes are sets of methods and variables that we can reuse in many places, so we want them to be generalized to a certain extent. We just need to be wary of making them too general.

Perhaps an example can shed more light on this...

Where Am I Going? ("Sweet Charity", Cy Coleman)

Suppose that we want to build a Helper Object that manages the status bar of Window Instances. It may contain methods for placing or removing text in various panes of the status bar, for turning a given status bar pane into a progress bar, etc. Depending on how and where we intend to deploy instances of this Object Class, we would address the target status bar in different ways in the code of the methods it contains.

For example, if we design this object to always be deployed as an instance (or even local) variable of a window to manage the status bar of only the window instance that contains it, we can address the status bar in the Object's methods using $cwind.$statusbar. When this object is deployed, if deployed according to design, its instance will always be housed within the window instance whose status bar it is intended to manage, so the $cwind notational shortcut points in precisely the right direction.

Perhaps one of the methods in this object is used to place a message in the first pane of the status bar. We might name this method $message1. If we know that this will always address the first pane of the status bar for the parent window of our Helper Object, the method would contain this code:

Calculate $cwind.$statusbar.$panes.1.$text as messagestring

The variable messagestring is a parameter of Character data type. If we wish to clear the first pane of the status bar, we can simply invoke this method and pass it an empty string. Only one parameter is needed here because our target can be narrowly and implicitly defined.

If we want to make this a bit more versatile, we could add a second parameter (panenumber) that specifies which pane should display the message string. This would most likely be a Short integer variable. We would make this parameter optional by assigning it a default (initial) value of 1. Having done this, our method line would have to change to:

Calculate $cwind.$statusbar.$panes.[panenumber].$text as messagestring

But maybe we don't want too many instances of this Object Class taking up RAM, yet we intend to possibly have many window instances simultaneously open on occasion. We might instead design this Helper Object to be deployed as a task variable. $cwind is no longer valid since the Object Instance now does not reside inside a window. We then have an additional decision to make. The question is, how will methods in this instance be invoked? Will they be called from within the window whose status bar needs assistance? Will they always apply only to the topmost window (which would make sense if they are always invoked from event handling methods)? Will we have to pass the name of the window instance or a reference to it in order for the object to know which status pane it is to affect? Depending on the answers to these questions, we might use either $topwind, $iwindows.[windowname] or an Item reference parameter variable to point the action in the proper direction. Then we have to remember to use this Object consistently and to pass in the proper parameter value(s) to indicate the target window instance, if that is necessary. The more clever we try to get with our deployment plans, the more parameters we need to supply to make certain that the job is done correctly.

We Can Do It ("The Producers ", Mel Brooks)

Let's try another example. A technique that was very popular in the Omnis 7 era was to open a window and perform some data entry process as part of the initialization procedure (procedure number zero) for that window. This was a fine thing to do in Omnis 7 because the window was constructed and drawn before this procedure began execution (which caused other objections and interesting workarounds from programmers - but that's another story). In Omnis Studio, this is not a good thing to do with the $construct method because the window instance is only supposed to be drawn once that method completes its work. Performing a process that requires any sort of data entry during the $construct method can cause problems and will most likely fail in one way or another.

But we still want to create processes that work like this - and there are good reasons to do so. For example, we might want to construct a dialog window that gathers data values or that prompts the user to make a selection as part of a larger process. How can we manage to do such things in Omnis Studio?

One way is to use a Helper Object. This one needs to sit outside the window instances (at least the ones it spawns) and manage them from there. The task level of scope is again useful for this purpose (although a strong argument can be made for using an instance variable of the window that requires the dialogs - if the Object only manages dialog windows). We might create a method in such an object named $openandexecute, whose job is to first open a window instance (our custom dialog window) and then (after the window construction process has been completed) invoke a method of that window (the data entry process). We may need to send this method a number of parameters as well, but the result is what we're after. Here is a brief summary of such a method:

First, our method needs a parameter for the name of the window class that is to be instantiated. Let's call it windowname. We can then use a method line like this to create the instance we need:

Do $clib.$windows.[windowname].$open() Returns winRef

This method instantiates the dialog window. The window's $construct method executes completely. We capture a reference to the new window instance in winRef (a local Item reference variable) so that we can further manipulate it from this method.

Now that we have an instance of the dialog window, we need to execute an appropriate public method of it (which will most likely contain a modal data entry phase to pause method execution while the user performs the task required by the dialog). We must also pass the name of the method we wish to execute as a parameter of our $openandexecute method. We will call this one methodname. The method must be a public one for us to execute it from a method of our Helper Object, so its name will begin with a dollar sign ($) character. We can either include this in the parameter string value we pass or exclude it and instead fix it permanently in the notation string used to execute that method. Depending on our choice here, one of the following two lines of code will execute the desired method:

Do winRef.[methodname]()
Do winRef.$[methodname]()

So if we have chosen the name dlog for the Object Variable that will contain our Object Instance (our Dialog Helper Object) at the task level of scope, if the name of our dialog window class is testDialog, if the name of the method we wish to execute once that window is instantiated is $enterdata and if we have chosen to use the second technique given above for executing that method, the method line in our existing window instance that invokes the dialog would be:

Do dlog.$openandexecute('testDialog','enterdata')

We can further complicate this by adding parameters to contain parameter strings to be sent to the $construct method and the named method of the dialog instance. You get the idea! All we need is a workable plan and a consistent execution of that plan for a reasonable chance of success.

Make Our Garden Grow ("Candide", Leonard Bernstein)

This all may seem a bit complicated at first, but this is the Omnis world in which we now live. Its rules govern how we make our methods work - and no amount of argument about how it should work some other way or it used to work some other way will change that. The lesson we must learn is to work with the flow of Omnis Studio and not against it. Learn the ways of Objects in Omnis Studio and have a long and productive development career!

The variety of uses we can find for Helper Objects is vast, so we can't possibly deal with all of them here. They are characterized by performing actions on other items within an application rather than returning values. Generally, the majority of variables they contain are parameter and local variables of their methods. There are few uses for instance variables within these Objects other than as constants or environment variables determined when an Object Instance is first constructed.

The Object Instances that rely heavily on their own instance variables are what I call Data Objects. We will discuss those when we return to the discussion of Object types in a couple of months.

So Long, Farewell ("The Sound of Music", Rodgers and Hammerstein)

Omnis Studio version 4.1 has now shipped and there are plenty of new and improved features to explore! We will take a break from Object Instances for a couple of months to see what's new with Omnis Studio 4.1. There are a number of useful new items not even mentioned in the What's New document - and many more just begging for more complete explanations and examples!

 

 
© 2006 Copyright of the text and images herein remains with the respective author. No part of this newsletter may be reproduced, transmitted, stored in a retrieval system or translated into any language in any form by any means without the written permission of the author or Omnis Software.
Omnis® and Omnis Studio® are registered trademarks, and Omnis 7™ is a trademark of Omnis Software UK Ltd. Other products mentioned are trademarks or registered trademarks of their corporations. All rights reserved.

Search Omnis Developer Resources

 

Hit enter to search

X