Tech News Back Issues Issue: 102005
Object Classes and Instances: The Basics
One subject that we have received a number of requests for explanations on is Object Classes. While some developers are happily propagating objects everywhere, others are completely baffled and would like some clarification. To that end, we will set out to make the use of object classes more accessible to a wider audience.
Interspersed with articles on Object Classes and Instances will be some articles on Window Classes. We have never really broached that subject in any meaningful way, yet we have assumed that windows were well understood in many of the examples provided over the years. We now know that this is not entirely true. Besides, the introduction of Object Classes to Omnis Studio offers us some new ways to take some of the processing burden off the windows in our applications and centralize some of that processing in reuseable objects that can be deployed as needed. So we will work along two parallel tracks for the next few months, hopefully making sense of how each of these class types work and of how we can use them together in meaningful ways.
Clarification of Terms
The term "object" is used to refer to several different things in Omnis Studio, depending on the context of the conversation. As is so often the case, there just aren't enough words in the language to give one to each new concept that comes along! So we'll have to employ a few adjectives and other descriptors to help us distinguish among these disparate items.
First, we have the term "object orientation". When used in relation to this concept, all items within the Omnis Studio domain that have properties and/or methods are "objects". They are hierarchically strung together in our libraries (each of which is itself an "object" as well) to form the very fabric of our applications. This is not the sense in which we will discuss "objects" in this current series of articles.
We then have "component objects" of windows, remote forms, menus, toolbars and reports in Omnis Studio. Windows, remote forms and reports can have "field objects" ($objs group) and "background objects" ($bobjs group - except for reports) , menus contain "line objects" ($objs group) and toolbars contain "tool objects" ($objs group). Again, this has nothing to do with the concept of an Object Class or an Object Instance.
The kind of "Object" we will discuss in this article is a special type of class within Omnis Studio. It is also a special type of instance that can be spawned from a class of that type. There are other items bearing the word "object" in their names that are related to this concept. We will also cover those subjects here as they become appropriate to the discussion.
So What Is an Object Class?
Of the many class types available in Omnis Studio, the Object class type is one of the more fascinating. An Object Class is a "faceless" (non-GUI) collection of methods and variables which is usually deployed (instantiated) as a scoped variable. We can use it in a number of ways, but we mainly use it in its instance form. This is because of the way this kind of "object" is generally accessed, as we will see later.
In some sense, an Object Class is similar to a Task Class - but only with regard to what it can contain. The purpose and use of an Object Class differs markedly from those of a Task Class, so I hope I don't cause any confusion pointing out the structural similarities between the two! Both contain methods and variables, but no component objects - and both are mainly useful as instances. That is the extent of their shared qualities, however.
As with many other class types (except Code, Search and the data description class types), an Object Class is the "blueprint" for instances. Object Instances are what do the work of this class type in a running application. But the way that Object Instances are deployed is quite different from other kinds of instances we have explored. We don't simply open them or install them in some visual location.
Spawning an Object Instance
An Object Class isn't of much use until we spawn an Object Instance from it. We design the Class, but we use the Instance. One of the intriguing things about Object Classes is that their instances are expressed and manipulated as variables. We use a variable of a special data type called "Object" to contain an instance of an Object Class at runtime. (We will discuss Object Reference variables in another article.) For right now, let's focus on the use of instantiated scoped variables (task, class, instance and local scope domains) in our initial explorations. File Class variables can also be given the Object data type, but this is usually for a different purpose than the one we will focus on here.
There are three techniques that we can use to instantiate an Object Instance. Here are brief explanations of static, dynamic and instantaneous instantiation:
We can most easily instantiate an Object Instance by creating a variable of Object type at the appropriate level of scope and then specifying an Object Class from our library as the subtype for that variable. This is all done in the Variables Pane of the Method Editor window for the class to which the Object variable belongs. That class will most likely be a Window, Report or Task Class, but it can be any code-bearing class - including an Object Class. (Yes, we can nest objects inside other objects. More on that subject some other time!) Here is what completed Object variable definitions look like:
We select the Object Class for the Subtype column entry by using a convenient dropdown list that shows all the Object Classes in our library as well as other items that can be used with this variable type (more on that later):
When an Object variable with a "complete" definition is first accessed by our code, it is ready to go. It contains an Object Instance (an instance of the Object Class specified in the Subtype column), complete with all of its methods and internal variables. The Initial Value Calculation for this Object variable (if we supply one) is evaluated and sent to the first parameter of the $construct method of the instance (if such a parameter exists there). We cannot send more than one parameter here because we must supply a valid expression in the Init. Val/Calc space - and that reduces to a single value. We cannot supply a comma-delimited value list here as we might in other places where we send parameters. If we try to do so and enclose that value list in quotes, it reduces to a single string value as far as Omnis Studio is concerned, so still only one parameter is sent.
But we have other options...
When we define a variable of Object type, we have the option to leave the subtype empty. If we had already specified an Object Class and now want to remove that earlier choice, we can instead choose the "<None>" option shown in the illustration above. Of course, the variable isn't at all useful in this state, but we can populate it dynamically in our code before using it. We perform this feat using the $new() method of the Object Class with an Object variable as the recipient of the return value of this method. Here is an example:
So why would we use this technique when we could simply assign an Object Class as the subtype in the Variables Pane and be done with it? I suppose that some developer somewhere might want to swap instances of different Object Classes into the same Object variable (and this can be done), but there is a more likely reason: The $new() method allows us to pass multiple parameters to the $construct method of the instance. We can't do that in the Variables Pane definition. The code line above was from the $construct method of a Window Class and was included there for precisely this reason.
In multiple library situations, a reference to the library that contains the Object Class must be included in the notation string. $objects is unambiguous in a single library application, but not when more than one library is open.
That would seem to be the only two ways to instantiate an Object. How could there be a third? Well, these are the only two ways to populate an Object variable, but who says we always need to use a variable?
There may be occasions when we feel that we don't need to hold an Object Instance in a variable. Perhaps we only need to use one of its methods in only one place in the code of a window, for example. In such a case, we can dynamically spawn the instance, use it and let it disperse again into the ether without taking up permanent residence in our Window Instance.
The $new() method can be followed by additional notation that specifies a public method within the Object Instance it spawns. The notation string is executed from left to right, so the $new() method is resolved (an Object instance is created) and any notation that follows in the same notation string then applies to that instance. For example:
This line creates a new (and ephemeral) instance of the demoObject class, passing two parameters to its $construct method. (The parentheses characters are still required even if we do not need to pass parameters to the constructor.) It then invokes the $test1 method of that instance and passes a parameter to that method as well. The return value generated by $test1 is the result of the expression represented by this notation string and that result is then assigned to variable.
What happens to the Object Instance? It simply vanishes since we did not put it anywhere. Wow! If we can do this, then why would we need to put an Object Instance into an Object variable in the first place?
Most often, we need persistence of some sort in our Object Instances. We choose the scope of Object variable to match the persistence we require. (Instantaneous instances are even less persistent than local variables.) But even for Function Objects (discussed below) that are only used as in the example above and do not carry variable values from one use to the next, there are still good reasons for using a variable instead of instantaneous instantiation. The main reason is that instantiation still requires a finite amount of time to accomplish. If we have a few places in a method where calls to methods in an Object must be made, each of those would require that the Object go through the instantiation process if we were to use this technique exclusively. That must be weighed against the cost of holding an Object Instance in a variable in RAM. Even though processor speeds are getting faster all the time, RAM is also getting cheaper. I still usually choose to use the RAM rather than the processing time, but it is good to have this option. Other people will have other opinions.
The Class Variable Difference
When an Object variable that is fully defined in the Variables Pane first comes into being, all of its public methods become available for notational access, its internal instance variable slots are created (and populated with initial values if those have been included in their definitions) and its $construct method is executed. This occurs when the Object variable is first accessed by code in the instance to which it belongs, just as is true for any other scoped variable. (A deeper discussion of the general variable construction process can be found in my book "Variables and Fields" in the Omnis Reference Library series, http://www.davidswain.com/newomniscience.html)
Most scoped variables cease to exist when the instance (or the method, in the case of local variables) to which they belong ceases to exist (or finishes execution). This applies to task, instance and local variables, but class variables persist (once they have been created) until the library to which their parent class belongs is closed. They also partially come into existence before they are referred to by executing code, so we can examine them through various means.
What do I mean by partial existence? Simply that the $construct method is not executed. Let's look at a simple example:
Create an Object Class named "demoObject". Give it an instance variable named "var1" of character type and give it an initial value of "testing".
Now create a Window Class named "objectTester". Give it a class variable named "demoObj" of object type and make demoObject the subtype Do not open a test instance of this window just yet, but bring the layout view of objectTester to the front and open the Catalog.
In the Catalog, select Class from the left list of the Variables tab. demoObj should appear there. Context-click (right mouse click on Windows, Control-click on Mac if you have a one-button mouse) on the variable name and select the first line of the context menu that appears. This should open the Value Window for demoObj, which in the case of an Object shows its internal variables - with the instance variables exposed by default. Notice that var1 already has its default value of "testing".
But how do we know the $construct method did not execute? Well, we don't at this point. We have to perform another experiment.
Open demoObject again and cut the initial value calculation so that it is now empty. (We cut it so we can paste it elsewhere instead of retyping it - just lazy, I guess.) In the $construct method, place the following line of code:
This instance variable of our Object must now rely on the $construct method of that Object for setting its initial value. But we have already brought our class variable (with all of its instance variables) into existence and it will persist until we close the library. So we must close and reopen our library to see the effect of our changes. After doing this and testing the demoObj class variable as before, we now see that the value of the var1 instance variable within demoObj is still empty:
This means that the $construct method, where the command to populate var1 now resides, did not execute. Maybe we just need to open a test instance of our objectTester window. But if we try that, we still get the same result. What to do?
The secret is that we haven't yet accessed demoObj from our code, so it has no need to construct itself. Let's make a couple more changes.
In the Object Class, create a method named "$test1" and give it the following line of code:
Now place a pushbutton object on the objectTester window and put the following code in its $event method:
If we open a test instance of this window once again, we see that var1 is still empty. But if we now click the pushbutton and check again (closing and reopening the Value Window might be necessary in some cases), we see that var1 now has its initial value of 'testing'. This is because demoObj was finally accessed by our code, so Omnis Studio was obliged to construct it for us and the $construct method executed.
As a byproduct of this experiment, notice that the name of the Object Instance is, in fact, the name of the variable that contains it. ($fullname shows us the actual path to that variable from $root.) Of course, this is only true for Object Instances contained in Object variables. Instantaneous instances have no variable from which to draw a name, so they are named using the class name followed by an underscore character and a number assigned by Omnis Studio. (We will see a similar behavior when we explore Object Reference variables in a later article.)
While this Object Instance will continue to exist until the library is closed, we can't do anything with it if all instances of the objectTester Window Class are closed. This is because we can only access a class variable of Object type (for application execution purposes) through an instance. And that is because of the way in which we generally use Object Instances - either to use their internal variables or to send messages using Notation. One reason we would use a class variable rather than an instance variable for our Object Instance is that a class variable is shared among all instances of the class to which it belongs. Most of us are less likely to use class variables that instance variables with our Object Instances in Omnis Studio, but I thought this behavior was otherwise instructive.
What's in a Name?
If we look closely at the variable context menu for demoObj in the Catalog, we will see something that is easy to miss. While the name of our instance ($cinst().$name) was determined above to be "demoObj" (the name of the Object variable), the value contained in the object variable is "Object demoObject" (the name of the class from which the instance was spawned - and probably the real, internal name of the instance as well):
So maybe an Object Instance held in a variable has two names: its reference name (the one we use to refer to it) and its instance name (determined by similar rules to those used to name instances of other class types). This will get more interesting as we explore further...
Performing Operations on Object Variables
While there are only three ways to instantiate an Object Instance, there are a few additional ways in which we can populate an Object variable. These involve using existing Object Instances and making copies of them into other variables.
For example, as with most other variables (except reference variables), we can use the Calculate and Do commands to make an exact clone of an existing Object Instance and put it into a different Object variable. Let's do an experiment in the window we have been using to demonstrate this.
Create a new instance variable of Object type. Name it "newObj" and leave its subtype empty. Now place another pushbutton field on the window and put the following code into the $event method of the pushbutton:
Now open a test instance of the window. First click our original pushbutton to make sure that demoObj is fully instantiated (we might want to examine the Variable Value window just to double-check), then click the new pushbutton. The OK message dialog appears announcing that the name of the current instance is "newObj", which indicates that our clone was successfully created.
But if we now examine the variable context menu for newObj, we see something really interesting:
The value contained within newObj is the name of the original Object Class followed by an underscore character and a 3-digit number! This looks like the kind of instance name we might expect if we were to specify "*" as the instance name for a window instance. (The variable tooltip for newObj also says "newObj = Object demoObject_171", both in the Catalog and the Notation Inspector.)
What is even more interesting is that if we now close the window instance, reopen it and perform the same operations, demoObj will now contain a value with the "random" (and slightly higher) number appended and newObj contains a value of "demoObject". Without trying to explain this further, suffice it to say that there is an internal instance name for the Object instance itself. We simply access the instance in practice through its container. We will see a similar situation when we discuss Object Reference variables.
For extra credit, go back and change the Calculate command to a Do command. Just select the Do command from the commands list and don't change anything else. (The result should be Do demoObj Returns newObj.) Testing will show that this works exactly the same as our original code using Calculate.
Besides using a calculation to clone an Object Instance, we can also pass the Object variable as a parameter of Object type. Again the result will be a clone of the original Object Instance (with a new internal name). While these clones are exact duplicates of the original at the time they are created, they have a life of their own from that point onward and are not tied to the original in any way. This can have its uses, but there are also ways that we can pass references to objects so that there is only one original that can be accessed from a number of locations. We just won't go further down that path for a couple of articles...
Uses of Object Instances
So far we have discussed how we create Object Classes and Instances without considering why we might want to do so. In general, we use their public methods and their instance and/or class variables (which are also publicly exposed) for various purposes within an application. These methods can be centrally maintained (in the Object Class) but used in various places throughout the application (through Object Instances of appropriate scope).
Why the emphasis on public methods of the instance? Well, in the Object Oriented way of programming, public methods are the ones exposed to the outside world. They are our means of communicating with an instance from outside that instance. Since Object Instances do not have visible component objects, public methods are our only way to communicate with an Object Instance.
In Omnis Studio, the public methods of an Object Instance (or any other instance, for that matter) can be executed like any other function through the use of Omnis Notation, unlike "universal" methods held in a Code Class. This means we can invoke these methods implicitly within any expression, not just explicitly using the Do command. This offers us tremendous flexibility in our coding!
We can also spawn multiple instances of an Object Class (perhaps within multiple instances of a window for which an Object variable is defined) that can contain different data views in their own instance variables. The imagination begins to kick in with additional possibilities the more we learn what can be done with Object Instances! While the use of Object Instances is a broad subject, we can focus the discussion a bit.
In my own work, I have identified four categories of uses for Object Instances. I'm sure this is not an exhaustive list of possible uses, but it seems like a good place to begin that discussion. My four categories are Function Objects, Helper Objects, Data Objects and External Objects. We will spend more time detailing these in subsequent articles, but here is a brief description of each category:
This is perhaps the simplest of the uses for Object Instances. A function object is a collection of public methods that return values. We can use them in much the same way as we use the built-in functions in Omnis Studio, hence the name. In practice, I group methods that perform related operations (financial functions, string functions, date functions, etc.) into Object Classes with names that describe that group and then deploy those functions where needed. If I need one group of functions in many places throughout a given task, I deploy the object as a task variable. If I only need those functions inside certain reports or windows, then I deploy instances of that object as instance variables in only those reports or windows that need it.
If I expect to have many instances of the same window open simultaneously, I might consider using a class variable instead of an instance variable for such an object. This is not for the persistence of internal variable values, but for space saving in RAM (no sense in having lots of copies of the same method collection floating around if I can help it!)
In the "mathematical" sense, a "function" is a method that accepts one of more values and returns a resulting value. While there will be exceptions to this, it is a generally descriptive rule. This means that a public method in a function object will usually have at least a Quit method line that generates a return value and will usually have at least one parameter variable. We will explore this all in the next article.
This use of Object Instances works more with manipulating objects within an application than it does with manipulating data values. A helper object is a collection of methods that perform actions, generally on objects (lower case "o") in the application. For example, an Object Instance might be built to manage multi-window processes. Such an Object might also use instance variables of its own for maintaining a list of the windows involved in the process, among other things. Again, we would be advised to cluster methods with related purposes into the same Object Class.
For those helper objects that help instances of various types communicate with one another, I prefer to have them instantiated outside the direct influence of any one of those instances. For that reason, I often choose to use task variables to house these Object instances. But there are also a number of helper objects that fit better of instance variables in the window or report where they are used.
A data object can be much more complex than a member of the two categories already mentioned. This is a collection of instance variables and methods that represent data constructs. I have determined two sub-categories: custom data types and data views.
A custom data type is an Object that generally manages only one data value (usually either a string or a number), but applies special formatting or other characteristics to that value. For example, we might create an Object Class to deal with degree-minute-second values for latitude and longitude. Or we might create one for handling SMTPE time code values for video work. Such data objects not only hold an intermediate display value for conversion to and from a value stored in the database, but they also contain the methods needed to perform that conversion as well as to perform various operations on that value type. This subject was addressed in an earlier Omnis Tech News article published January 9, 2002.
A data view object contains one or more row or list variables that maintain record images from the database - often from different files or tables. Such an Object could represent a single database table or a collection of related tables, complete with all the business rules for how they relate to one another. So we could have an Invoice Object, for example, that handles all the records that comprise an invoice - perhaps spread over half a dozen tables - and contains all the methods we need to perform any operation regarding those tables. We could then use an instance of that Object anywhere within our application where we need to deal with invoices. The code is maintained in one location, but we can use that code from within any window or report. This demonstrates the potential power of Object Instances in Omnis Studio.
There are a number of external components that are designed to work as Objects with Omnis Studio. We can create Object Classes or Object variables that inherit methods and variables (properties) from these specially-built external components. We can then deploy instances of these Objects at appropriate levels of scope wherever we need their assistance.
For example, there is a Timer external that we can use in this way. This allows us to deploy multiple timers with different countdown periods. The FileOps external has an Object component to it, as does the Graph external. There are also a number of useful external components of Object type that ship with Omnis Studio, as well as third party products available for purchase.
Most Object-type external components can be either the superclass for an Object Class or directly set as the subtype for an Object variable. The Timer object is best subclassed as an Object Class first, since its $timer method must be overridden for it to do anything when the timer countdown is complete. Of course, this can also be done in our code, but modifying the code in an Object Class is more straightforward. Subclassing an Object Class from an external object also has the educational advantage that we can then use the Interface Manager to explore all of its properties and methods.
The modern Object DAMs also fall into this category. So we create external objects to access SQL data sources in Omnis Studio.
In practice, some of these uses may be combined within a single Object Class. For example, a Data Object might very well contain a few functions that are specific to the type of data the object is designed to manipulate, but that can also be invoked from outside the Object to handle such values from any source.
In the next article in this series, we will examine the four categories of Object Instance uses mentioned above in more detail. I hope you have found the discussion so far to be intriguing enough to come back and read more...
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® and Omnis Studio® are registered trademarks, and Omnis 7 is a trademark of Raining Data UK Ltd. Other products mentioned are trademarks or registered trademarks of their corporations. All rights reserved.