Using Caché Objects
Working with Registered Objects
[Back] [Next]
   
Server:docs2
Instance:LATEST
User:UnknownUser
 
-
Go to:
Search:    

The %RegisteredObject class is the basic object API in Caché. This chapter describes how to use this API. Information in this chapter applies to all subclasses of %RegisteredObject.

Also see the chapter Working with Persistent Objects.”
When viewing this book online, use the preface of this book to quickly find other topics.
Introduction to Object Classes
An object class is any class that inherits from %RegisteredObject. With an object class, you can do the following things:
These tasks are possible only with object classes.
The classes %Persistent and %SerialObject are subclasses of %RegisteredObject. These classes are described in later chapters. Also, for an overview, see Object Classes in the chapter Defining and Compiling Classes.”
OREF Basics
When you create an object, Caché creates an in-memory structure, which holds information about that object, and also creates an OREF (object reference), which is a pointer to that structure.
The object classes provide several methods that create OREFs. When you work with any of the object classes, you use OREFs extensively. You use them when you specify values of properties of an object, access values of properties of the object, and call instance methods of the object. Consider the following example:
SAMPLES>set person=##class(Sample.Person).%New()
 
SAMPLES>set person.Name="Carter,Jacob N."
 
SAMPLES>do person.PrintPerson()
 
Name: Carter,Jacob N.
In the first step, we call the %New() method of the Sample.Person class, which creates an object and returns an OREF that points to that object. We set the variable person equal to this OREF. In the next step, we set the Name property of object. In the third step, we invoke the PrintPerson() instance method of the object. (Note that the Name property and the PrintPerson() method are both just examples—these are defined in the Sample.Person class but are not part of the general object interface.)
An OREF is transient; the value exists only while the object is in memory and is not guaranteed to be constant over different invocations.
INVALID OREF Error
In simple expressions, if you try to set a property, access a property, or invoke an instance method of a variable that is not an OREF, you receive an <INVALID OREF> error. For example:
SAMPLES>write p2.PrintPerson()
 
WRITE p2.PrintPerson()
^
<INVALID OREF>
SAMPLES>set p2.Name="Dixby,Jase"
 
SET p2.Name="Dixby,Jase"
^
<INVALID OREF>
Note:
The details are different when the expression has a chain of OREFs; see Introduction to Dot Syntax.”
Testing an OREF
Caché provides a function, $ISOBJECT, which you can use to test whether a given variable holds an OREF. This function returns 1 if the variable contains an OREF and returns 0 otherwise. If there is an chance that a given variable might not contain an OREF, it is good practice to use this function before trying to set a property, access a property, or invoke an instance method of the variable.
OREFs, Scope, and Memory
Any given OREF is a pointer to an in-memory object to which other OREFs might also point. That is, the OREF (which is a variable) is distinct from the in-memory object (although, in practice, the terms OREF and object are often used interchangeably).
Note the following points:
Removing an OREF
If needed, to remove an OREF, use the KILL command:
 kill OREF
Where OREF is a variable that contains an OREF. This command removes the variable. If there are no further references to the object, this command also removes the object from memory, as discussed earlier.
OREFs, the SET Command, and System Functions
For some system functions (for example, $Piece, $Extract, and $List), Caché supports an alternative syntax that you can use to modify an existing value. This syntax combines the function with the SET command as follows:
 SET function_expression = value
Where function_expression is a call to the system function, with arguments, and value is a value. For example, the following statement sets the first part of the colorlist string equal to "Magenta":
 SET $PIECE(colorlist,",",1)="Magenta"
It is not supported to modify OREFs or their properties in this way.
Creating New Objects
To create a new instance of a given object class, use the class method %New() of that class. This method creates an object and returns an OREF. The following shows an example:
 Set person = ##class(MyApp.Person).%New()
The %New() method accepts an argument, which is ignored by default. If present, this argument is passed to %OnNew() callback method of the class, if defined. If %OnNew() is defined, it can use the argument to initialize the newly created object in some way. For details, see Implementing Callback Methods,” later in this book.
If you have complex requirements that affect how you create new objects of given class, you can provide an alternative method to be used to create instances of that class. Such a method would call %New() and then would initialize properties of the object as needed. Such a method is sometimes called a factory method.
Viewing Object Contents
The WRITE command writes output of the following form for an OREF:
n@Classname
Where Classname is the name of the class, and n is an integer that indicates a specific instance of this class in memory. For example:
SAMPLES>write p
8@Sample.Person
If you use the ZWRITE command with an OREF, Caché displays more information about the associated object.
SAMPLES>zwrite p
p=<OBJECT REFERENCE>[8@Sample.Person]
+----------------- general information ---------------
|      oref value: 1
|      class name: Sample.Person
|           %%OID: $lb("3","Sample.Person")
| reference count: 2
+----------------- attribute values ------------------
|       %Concurrency = 1  <Set>
|                DOB = 33589
|               Name = "Clay,George O."
|                SSN = "480-57-8360"
+----------------- swizzled references ---------------
|   i%FavoriteColors = ""  <Set>
|   r%FavoriteColors = ""  <Set>
|             i%Home = $lb("5845 Washington Blvd","St Louis","NM",55683)  <Set>
|             r%Home = ""  <Set>
|           i%Office = $lb("3413 Elm Place","Pueblo","WI",98532)  <Set>
|           r%Office = ""  <Set>
|           i%Spouse = ""
|           r%Spouse = ""
+-----------------------------------------------------
Notice that this information displays the class name, the OID, the reference count, and the current values (in memory) of properties of the object. In the section swizzled references, the items with names starting i% are instance variables, which are discussed later in this chapter. (The items with names starting r% are for internal use only.)
Introduction to Dot Syntax
With an OREF, you can use dot syntax to refer to properties and methods of the associated object. This section introduces dot syntax, which is also discussed in later sections, along with alternative ways to refer to properties and methods of objects.
The general form of dot syntax is as follows:
oref.membername
For example, to specify the value of a property for an object, you can use a statement like this:
 Set oref.PropertyName = value
where oref is the OREF of the specific object, PropertyName is the name of the property that you want to set, and value is a Caché ObjectScript expression that evaluates to the desired value. This could be a constant or could be a more complex expression.
We can use the same syntax to invoke methods of the object (instance methods). An instance method is invoked from a specific instance of a class and typically performs some action related to that instance. In the following example, we invoke the PrintPerson() method of an object:
 set person=##class(Sample.Person).%New() 
 set person.Name="Carter,Jacob N." 
 do person.PrintPerson()
 
If the method returns a value, you can use the SET command to assign the returned value to a variable:
SET myvar=oref.MethodName()
If the method does not return a value (or if you are uninterested in the return value), use either DO or JOB:
Do oref.MethodName()
If the method accepts arguments, specify them within the parentheses.
 Set value = oref.methodName(arglist)
Cascading Dot Syntax
Depending on the class definition, a property can be object-valued, meaning that its type is an object class. In such cases, you can use a chain of OREFs to refer to a property of the properties (or to a method of the property). This is known as cascading dot syntax. For example, the following syntax refers to the Street property of the HomeAddress property of a Person object:
 set person.HomeAddress.Street="15 Mulberry Street"
In this example, the person variable is an OREF, and the expression person.HomeAddress is also an OREF.
Note:
When referring to a class member generally, sometimes the following informal reference is used: PackageName.ClassName.Member, for example, the Accounting.Invoice.LineItem property. This form never appears in code.
Cascading Dot Syntax with a Null OREF
When you use a chain of OREFs to refer to a property or to a method, if an intermediate property is not a valid OREF, Caché throws an <INVALID OREF> error in some, but not all scenarios. Consider the following expression:
x.y.z
Where x is a valid OREF, y is the name of an object-valued property of x, and z is a property or method of that property. The following table lists the possible scenarios that occur when y does not hold a valid OREF; these scenarios depend whether the property y is based on a serial object. (For information on serial objects, see the chapter Defining and Using Object-Valued Properties.”)
Scenario Property y is not a serial object Property y is a serial object
write x.y.z Writes a null value Writes the initial expression for the property z
set x.y.z="some value" <INVALID OREF> error The property is set.
do x.y.z() <INVALID OREF> error The method is executed.
Validating Objects
The %RegisteredObject class provides a way to validate the properties of an instance. An object is valid if all of the following are true:
To determine whether a given object is valid, call its %ValidateObject() method. If this method returns 1, then the object is valid. If it returns an error status, the object is not valid. The following shows an example:
    #Include %occStatus
    set person=##class(Sample.Person).%New()
    set person.DOB="December 12 1990"
    set status=person.%ValidateObject()
    write !, "First try"
    if $$$ISERR(status) {
        do $system.OBJ.DisplayError(status)
    } else {
        write !, "Object is valid"
    }

    set person.Name="Ellsworth,Myra Q."
    set person.SSN="000-00-0000"
    set person.DOB=$zdateh("December 12 1990",5)
    set status=person.%ValidateObject()
    write !!, "Second try"
    if $$$ISERR(status) {
        do $system.OBJ.DisplayError(status)
    } else {
        write !, "Object is valid"
    }
If you run this example, you will see the following output:
First try
ERROR #7207: Datatype value 'December 12 1990' is not a valid number
  > ERROR #5802: Datatype validation failed on property 'Sample.Person:DOB', with value equal to "December 12 1990"
ERROR #5659: Property 'Sample.Person::Name(1@Sample.Person,ID=)' required
ERROR #5659: Property 'Sample.Person::SSN(1@Sample.Person,ID=)' required
ERROR #7209: Datatype value '' does not match PATTERN '3N1"-"2N1"-"4N'
  > ERROR #5802: Datatype validation failed on property 'Sample.Person:SSN', with value equal to ""
 
Second try
Object is valid
Note that %ValidateObject() in turn calls the validation logic for each property; see Using and Overriding Property Methods later in this book.
For persistent objects (introduced in the next chapter), when you save an object, Caché automatically calls %ValidateObject() method first. If the object is not valid, Caché does not save it.
Determining an Object Type
Given an object, the %RegisteredObject class provides methods to determine its inheritance. This section discusses them.
%Extends()
To check if an object inherits from a specific superclass, call its %Extends() method, and pass the name of that superclass as the argument. If this method returns 1, then the instance inherits from that class. If it returns 0, the instance does not inherit from that class. For example:
SAMPLES>set person=##class(Sample.Person).%New()
 
SAMPLES>w person.%Extends("%RegisteredObject")
1
SAMPLES>w person.%Extends("Sample.Person")
1
SAMPLES>w person.%Extends("Sample.Employee")
0
%IsA()
To check if an object has a specific class as its primary superclass, call its %IsA() method, and pass the name of that superclass as the argument. If this method returns 1, the object does have the given class as its primary superclass.
%ClassName() and the Most Specific Type Class (MSTC)
Although an object may be an instance of more than one class, it always has a most specific type class (MSTC). A class is said to be the most specific type of an object when that object is an instance of that class and is not an instance of any subclass of that class.
For example, in the case of the GradStudent class inheriting from the Student class that inherits from the Person class, for instances created by the commands:
 set MyInstance1 = ##class(MyPackage.Student).%New()
 set MyInstance2 = ##class(MyPackage.GradStudent).%New()
MyInstance1 has Student as its MSTC, since it is an instance of both Person and Student, but not of GradStudent. MyInstance2 has GradStudent as its MSTC, since it is an instance of GradStudent, Student, and Person.
The following rules also apply regarding the MSTC of an object:
To determine the MSTC of an object, use the %ClassName() method, which is inherited from %RegisteredObject
classmethod %ClassName(fullname As %Boolean) as %String
Where fullname is a boolean argument where 1 specifies that the method return a package name and class name and 0 (the default) specifies that the method return only the class name.
For example:
 write myinstance.%ClassName(1)
(Similarly, you can use %PackageName() to get just the name of the package.)
Cloning Objects
To clone an object, call the %ConstructClone() method of that object. This method creates a new OREF.
The following Terminal session demonstrates this:
SAMPLES>set person=##class(Sample.Person).%OpenId(1)
 
SAMPLES>set NewPerson=person.%ConstructClone()
 
SAMPLES>w
 
NewPerson=<OBJECT REFERENCE>[2@Sample.Person]
person=<OBJECT REFERENCE>[1@Sample.Person]
SAMPLES>
Here, you can see that the NewPerson variable uses a different OREF than the original person object. NewPerson is a clone of person (or more precisely, these variables are pointers to separate but identical objects).
In contrast, consider the following Terminal session:
SAMPLES>set person=##class(Sample.Person).%OpenId(1)
 
SAMPLES>set NotNew=person
 
SAMPLES>w
 
NotNew=<OBJECT REFERENCE>[1@Sample.Person]
person=<OBJECT REFERENCE>[1@Sample.Person]
Notice that here, both variables refer to the same OREF. That is, NotNew is not a clone of person.
For information on arguments to this method, see the InterSystems Class Reference for %Library.RegisteredObject.
Referring to Properties of an Instance
To refer to a property of an instance, you can do any of the following:
Calling Methods of an Instance
To call a method of an instance, you can do any of the following:
Obtaining the Class Name from an Instance
To obtain the name of a class, use the $CLASSNAME function:
$CLASSNAME(oref)
where oref is an OREF.
For more information, see the $CLASSNAME page in the Caché ObjectScript Reference.
$this Variable (Current Instance)
The $this syntax provides a handle to the OREF of the current instance, such as for passing it to another class or for another class to refer to properties of methods of the current instance. When an instance refers to its properties or methods, the relative dot syntax is faster and thus is preferred.
Note:
$this is not case-sensitive; hence, $this, $This, $THIS, or any other variant all have the same value.
For example, suppose there is an application with an Accounting.Order class and an Accounting.Utils class. The Accounting.Order.CalcTax() method calls the Accounting.Utils.GetTaxRate() and Accounting.Utils.GetTaxableSubtotal() methods, passing city and state of the current instance to the GetTaxRate() method and passing the list of items ordered and relevant tax-related information to GetTaxableSubtotal(). CalcTax() then uses the values returned to calculate the sales tax for the order. Hence, its code is something like:
Method CalcTax() As %Numeric
{
    Set TaxRate = ##class(Accounting.Utils).GetTaxRate($this)
    Write "The tax rate for ",..City,", ",..State," is ",TaxRate*100,"%",!
    Set TaxableSubtotal = ##class(Accounting.Utils).GetTaxableSubTotal($this)
    Write "The taxable subtotal for this order is $",TaxableSubtotal,!
    Set Tax = TaxableSubtotal * TaxRate
    Write "The tax for this order is $",Tax,!
}
The first line of the method uses the ##Class syntax (described earlier) to invoke the other method; it passes the current object to that method using the $this syntax. The second line of the method uses the .. syntax (also described earlier) to get the values of the City and State properties. The action on the third line is similar to that on the first line.
In the Accounting.Utils, the GetTaxRate() method can then use the handle to the passed-in instance to get handles to various properties — for both getting and setting their values:
ClassMethod GetTaxRate(OrderBeingProcessed As Accounting.Order) As %Numeric
{
    Set LocalCity = OrderBeingProcessed.City
    Set LocalState = OrderBeingProcessed.State
    // code to determine tax rate based on location and set
    // the value of OrderBeingProcessed.TaxRate accordingly
    Quit OrderBeingProcessed.TaxRate
}
The GetTaxableSubtotal() method also uses the handle to the instance to look at its properties and set the value of its TaxableSubtotal property.
Hence, if we invoke the CalcTax() method of MyOrder instance of the Accounting.Order class, we would see something like this:
>Do MyOrder.CalcTax()
The tax rate for Cambridge, MA is 5%
The taxable subtotal for this order is $79.82
The tax for this order is $3.99
i%PropertyName (Instance Variables)
This section introduces instance variables. You do not need to refer to these variables unless you override an accessor method for a property; see the chapter Using and Overriding Property Methods.”
When you create an instance of any class, Caché creates an instance variable for each non-calculated property of that class. The instance variable holds the value of the property. For the property PropName, the instance variable is named i%PropName, and this variable name is case-sensitive. These variables are available within any instance method of the class.
For example, if a class has the properties Name and DOB, then the instance variables i%Name and i%DOB are available within any instance method of the class.
Internally, Caché also uses additional instance variables with names such as r%PropName and m%PropName, but these are not supported for direct use.
Instance variables have process-private, in-memory storage allocated for them. Note that these variables are not held in the local variable symbol table and are not affected by the Kill command.