Caché Programming Orientation Guide
Caché Classes
[Back] [Next]
   
Server:docs1
Instance:LATEST
User:UnknownUser
 
-
Go to:
Search:    

It is useful to review the basic rules for working with and creating classes in general, before looking at the object classes in particular. Thus this chapter discusses the basic rules for defining and working with classes in Caché. It discusses the following topics:

The next chapter discusses objects and object classes. Namespaces are discussed later in this book.
Class Names and Packages
Each Caché class has a name, which must be unique within the namespace where it is defined. A full class name is a string delimited by one or more periods, as in the following example: package.subpackage.subpackage.class. The short class name is the part after the final period within this string; the part preceding the final period is the package name.
The package name is simply a string, but if it contains periods, the Caché development tools treat each period-delimited piece as a subpackage. Studio and other tools display these subpackages as a hierarchy of folders, for convenience. For example, the Namespace tab of the Studio Workspace window displays packages like this:
Note that Project tab of the Studio Workspace window shows the contents of the currently selected Studio project. In this tab, if the project includes the entire package or subpackage, the package icon is a folder with slanted blue lines. If only some classes in the package are in the project, Studio uses the usual icon. The following shows an example:
This book does not discuss projects in any detail; for information, see Using Cache Studio.
Studio provides options for exporting individual classes, entire packages, and projects to XML files, as well as for importing those items from XML files.
Basic Contents of a Class Definition
A Caché class definition can include the following items, all known as class members:
A class definition can include keywords; these affect the behavior of the class compiler. You can specify some keywords for the entire class, and others for specific class members. These keywords affect the code that the class compiler generates and thus control the behavior of the class.
The following shows a simple Caché class definition:
Class MyApp.Main.SampleClass Extends %RegisteredObject
{

Parameter CONSTANTMESSAGE [Internal] = "Hello world!" ;

Property VariableMessage As %String [ InitialExpression = "How are you?"];

Property MessageCount As %Numeric [Required];

ClassMethod HelloWorld() As %String 
 {
    Set x=..#CONSTANTMESSAGE
    Quit x
 }

Method WriteIt() [ ServerOnly = 1]
{
    Set count=..MessageCount
    For i=1:1:count {
        Write !,..#CONSTANTMESSAGE," ",..VariableMessage
        }
    }

}
Note the following points:
The following Terminal session shows how we can use this class:
SAMPLES>write ##class(MyApp.Main.SampleClass).HelloWorld()
Hello world!
SAMPLES>set x=##class(MyApp.Main.SampleClass).%New()
 
SAMPLES>set x.MessageCount=3
 
SAMPLES>do x.WriteIt()
 
Hello world! How are you?
Hello world! How are you?
Hello world! How are you?
Class Name Shortcuts
When referring to a class, you can omit the package (or the higher level packages) in the following scenarios:
Class Parameters
A class parameter defines a value that is the same for all objects of a given class. This value is established when the class is compiled and cannot be altered at runtime. You use class parameters for the following purposes:
The following shows a class with several parameters:
Class GSOP.DivideWS Extends %SOAP.WebService
{

Parameter USECLASSNAMESPACES = 1;

///  Name of the Web service.
Parameter SERVICENAME = "Divide";

///  SOAP namespace for the Web service
Parameter NAMESPACE = "http://www.mynamespace.org";

/// let this Web service understand only SOAP 1.2
Parameter SOAPVERSION = "1.2";

 ///further details omitted
}
Properties
Formally, there are two kinds of properties in Caché:
This section shows a sample class that contains property definitions that show some of these variations:
Class MyApp.Main.Patient Extends %Persistent
{

Property PatientID As %String [Required];

Property Gender As %String(DISPLAYLIST = ",Female,Male", VALUELIST = ",F,M");

Property BirthDate As %Date;

Property Age As %Numeric [Transient];

Property MyTempArray [MultiDimensional];

Property PrimaryCarePhysician As Doctor;

Property Allergies As list Of PatientAllergy;

Relationship Diagnoses As PatientDiagnosis [ Cardinality = children, Inverse = Patient ]; 
}
Note the following:
Object-valued properties and relationship properties are discussed in the next chapter.
Specifying Property Keywords
In a property definition, you can include optional property keywords that affect how the property is used. The following list shows some of the most commonly seen keywords:
Required
Specifies that the value of the property set before an instance of this class can be stored to disk. By default, properties are not required. In a subclass, you can mark an optional property as required, but you cannot do the reverse.
InitialExpression
Specifies an initial value for the property. By default, properties have no initial value. Subclasses inherit the value of the InitialExpression keyword and can override it. The value specified must be a valid Caché ObjectScript expression (this applies even if the class is written in another language, such as Caché MVBasic).
Transient
Specifies that the property is not stored in the database. By default, properties are not transient. Subclasses inherit the value of the Transient keyword and cannot override it.
Private
Specifies that the property is private. Subclasses inherit the value of the Private keyword and cannot override it.
By default, properties are public and can be accessed anywhere. You can mark a property as private (via the Private keyword). If so, it can only be accessed by methods of the object to which it belongs.
In Caché, private properties are always inherited and visible to subclasses of the class that defines the property.
In other programming languages, these are often called protected properties.
Calculated
Specifies that the property has no in-memory storage allocated for it when the object containing it is instantiated. By default, a property is not calculated. Subclasses inherit the Calculated keyword and cannot override it.
MultiDimensional
Specifies that the property is multidimensional. This property is different from other properties as follows:
Multidimensional properties are rare but are occasionally useful to temporarily contain object state information.
Properties Based on Data Types
When you define a property and you specify its type as a data type class, you have special options for defining and working with that property, as described in this section.
Data Type Classes
Data type classes enable you to enforce sets of rules about the values of properties.
Caché provides data type classes which include %Library.String, %Library.Integer, %Library.Numeric, %Library.Date, %MV.Date, and many others. Because the names of classes of the %Library package can be abbreviated, you can abbreviate many of these; for example, %Date is an abbreviation for %Library.Date.
Each data type class has the following features:
You can add your own data type classes. For example, the following shows a custom subclass of %Char:
Class MyApp.MyType Extends %Library.Char
{

/// The maximum number of characters the string can contain.
Parameter MAXLEN As INTEGER = 2000;

}
Overriding Parameters of Data Type Classes
When you define a property and you specify its type as a data type class, you can override any parameters defined by the data type class.
For example, the %Integer data type class defines the class parameter (MAXVAL) but provides no value for this parameter. You can override this in a property definition as follows:
Property MyInteger As %Integer(MAXVAL=10);
For this property, the maximum allowed value is 10.
(Internally, this works because the validation methods for the data type classes are method generators; the parameter value you provide is used when the compiler generates code for your class. Method generators are discussed later in Special Kinds of Methods.”)
Similarly, every property of type %String has a collation type, which determines how values are ordered (such as whether capitalization has effects or not). The default collation type is SQLUPPER. For more details on collations, see the section Data Collation in the chapter “Caché SQL Basics” in Using Caché SQL.
For another example, the data type classes define the DISPLAYLIST and VALUELIST parameters, which you can use to specify choices to display in a user interface and their corresponding internal values:
Property Gender As %String(DISPLAYLIST = ",Female,Male", VALUELIST = ",F,M");
Using Instance Variables
To access the in-memory value of a property from within an instance method of an object, you can use the following in-memory value syntax:
 Set i%Name = "Carl"
This directly sets “Carl” as the in-memory value of the property Name, bypassing the NameSet accessor method (if present). The variable i%Name is an instance variable; see i%<PropertyName> syntax in Using Caché Objects. For information on accessor methods, see see the chapter Using and Overriding Property Methods in the same book.
Using Other Property Methods
Properties have a number of methods associated with them automatically. These methods are generated by the data type classes.
For example, if we define a class Person with three properties:
Class MyApp.Person Extends %Persistent
{
Property Name As %String;
Property Age As %Integer;
Property DOB As %Date;
}
The names of each generated method is the property name concatenated with the name of the method from the inherited class. For example, some of the methods associated with the DOB property are:
 Set x = person.DOBIsValid(person.DOB)
 Write person.DOBLogicalToDisplay(person.DOB)
where IsValid is a method of the property class and LogicalToDisplay is a method of the %Date data type class.
Methods
There are two kinds of methods: instance methods and class methods (called static methods in other languages). In most cases, a method is a procedure.
Specifying Method Keywords
In a method definition, you can include optional compiler keywords that affect how the method behaves. The following list shows some of the most commonly seen method keywords:
ProcedureBlock
By default, the variables used in a method are private to that method, because by default all methods are procedure blocks. To define a method as a non-procedure block, specify the ProcedureBlock keyword as 0. For example:
Method MyMethod() [ ProcedureBlock = 0 ] 
{
    //implementation details
}
In this case, the variables in this method would be public variables.
Language
You have the choice of implementation language when creating a server-side method in Caché. The options are basic (Caché Basic), cache (Caché ObjectScript), mvbasic (MVBasic), and tsql (TSQL).
By default, a method uses the language specified by the Language keyword specified for the class. In most cases, that keyword is cache (Caché ObjectScript).
Private
Specifies that the method is private. Subclasses inherit the value of the Private keyword and cannot override it.
By default, methods are public and can be accessed anywhere. You can mark a method as private (via the Private keyword). If you do:
It is, however, inherited and available in subclasses of the class that defines the method.
Other languages often call such methods protected methods.
References to Other Class Members
Within a method, use the syntax shown here to refer to other class members:
References to Methods of Other Classes
Within a method (or within a routine), use the syntax shown here to refer to a method in some other class:
Not all methods have return values, so choose the syntax appropriate for your case.
References to Current Instance
Within an instance method, sometimes it is necessary to refer to the current instance itself, rather than to a property of method of the instance. For example, you might need to pass the current instance as an argument when invoking some other code. In such a case, use the special variable $THIS to refer to the current instance.
For example:
 Set sc=header.ProcessService($this)
Method Arguments
A method can take positional arguments in a comma-separated list. For each argument, you can specify a type and the default value.
For instance, here is the partial definition of a method that takes three arguments:
Method Calculate(count As %Integer, name, state As %String = "CA") as %Numeric
{
    // ...
}
Notice that two of the arguments have explicit types, and one has an default value. Generally it is a good idea to explicitly specify the type of each argument.
Skipping Arguments
In Caché ObjectScript, when you invoke a method, you can skip arguments, if there are suitable defaults for them. For example, the following is valid:
 set myval=##class(mypackage.myclass).GetValue(,,,,,,4)
Passing Variables by Value or by Reference
When you invoke a method, you can pass values of variables to that method either by value or by reference, in just the same way that you do with routines and subroutines; see Passing Variables by Value or by Reference,” earlier in this book:
The signature of a method usually indicates whether you are intended to pass arguments by reference. For example:
Method MyMethod(argument1, ByRef argument2, Output argument3)
The ByRef keyword indicates that you should pass this argument by reference. The Output keyword indicates that you should pass this argument by reference and that the method ignores any value that you initially give to this argument.
Similarly, when you define a method, you use the ByRef and Output keywords in the method signature to inform other users of the method how it is meant to be used.
Important:
The ByRef and Output keywords provide information for the benefit of anyone using the InterSystems Class Reference, introduced later. They do not affect the behavior of the code. It is the responsibility of the writer of the method to enforce any rules about how the method is to be invoked.
Variable Numbers of Arguments
You can define a method so that it accepts a variable number of arguments. For example:
ClassMethod MultiArg(Arg1... As %List)
{
 Write "Invocation has ",
     $GET(Arg1, 0),
     " element",
     $SELECT(($GET(Arg1, 0)=1):"", 1:"s"),
     !
 For i = 1 : 1 : $GET(Arg1, 0)
 {
     Write:($DATA(Arg1(i))>0) "Argument[", i , "]:", 
         ?15, $GET(Arg1(i), "<NULL>"), !
 }
 Quit
}
Because methods are procedures, they support the ... syntax to accept variable numbers of arguments. This syntax is described in the Variable Numbers of Arguments section of the “User-defined Code” chapter of Using Caché ObjectScript.
Specifying Default Values
To specify an argument’s default value, use syntax as shown in the following example:
Method Test(flag As %Integer = 0)
{
 //method details
}
When a method is invoked, it uses its default values (if specified) for any missing arguments.
Another option is to use the $GET function. For example:
Method Test(flag As %Integer)
{
  set flag=$GET(flag,0)
 //method details
}
This technique, however, does not affect the class signature.
Special Kinds of Methods
The CodeMode keyword enables you to define other, special kinds of methods:
Call Methods
A call method is a special mechanism to create method wrappers around existing Caché routines. The syntax for a call method is as follows:
Method Call() [ CodeMode = call ]
{
    Label^Routine
}
where “Label^Routine” specifies a label within a routine.
Method Generators
A method generator is a program that is invoked by the class compiler during class compilation. Its output is the actual runtime implementation of the method. Method generators provide a means of inheriting methods that can produce high performance, specialized code that is customized to the needs of the inheriting class or property. Within the Caché library, method generators are used extensively by the data type and storage classes.
For details, refer to the Method Generators chapter of Using Caché ObjectScript.
Class Queries
A Caché class can contain class queries. A class query defines an SQL query that can be used by the class and specifies a class to use as a container for the query. The following shows an example:
Query QueryName(Parameter As %String) As %SQLQuery
{
SELECT MyProperty, MyOtherProperty FROM MyClass
 WHERE (MyProperty = "Hello" AND MyOtherProperty = :Parameter)
 ORDER BY MyProperty
}
You define class queries to provide predefined lookups for use in your application. For example, you can look up instances by some property, such as by name, or provide a list of instances that meet a particular set of conditions, such as all the flights from Paris to Madrid. The example shown here uses a parameter, which is a common way to provide a flexible query. Note that you can define class queries within any class; there is no requirement to include class queries within persistent classes, which are introduced later in this book.
XData Blocks
Because XML is often a useful way to represent structured data, Caché classes include a mechanism that allow you to include well-formed XML documents, for any need you might have. To do this, you include an XData block, which is another kind of class member. The following shows an example:
The following shows an example:
XData Contents [XMLNamespace="http://www.intersystems.com/zen"]
  {
    <page xmlns="http://www.intersystems.com/zen" title="HelpDesk">
      <html id="title">My Title</html>
      <hgroup>
        <pane paneName="menuPane"/>
        <spacer width="20"/>
        <vgroup width="100%" valign="top">
          <pane paneName="tablePane"/>
          <spacer height="20"/>
          <pane paneName="detailPane"/>
        </vgroup>
      </hgroup>
    </page>
  }
Caché uses XData blocks for certain specific purposes, and these might give you ideas for your own applications:
Note that in all these cases, the XData block must be included within a class of a specific type.
Macros and Include Files in Class Definitions
In a Caché class definition, you can define macros in a method and use them in that method. More often, however, you define them in an include file, which you can include at the start of any class definition. For example:
Include (%assert, %callout, %occInclude, %occSAX)

/// Implements an interface to the XSLT Parser. XML contained in a file or binary stream 
/// may be transformed
Class %XML.XSLT.Transformer Extends %RegisteredObject ...
Then your methods in that class can refer to any macros defined in that include file, or in its included include files.
Macros are inherited. That is, a subclass has access to all the same macros as its superclasses.
Inheritance Rules in Caché
As with other class-based languages, you can combine multiple class definitions via inheritance. A Caché class definition can extend (or inherit from) multiple other classes. Those classes, in turn, can extend other classes.
The following subsections provide the basic rules for inheritance of classes in Caché.
Inheritance Order
Caché uses the following rules for inheritance order:
  1. By default, if a class member of a given name is defined in multiple superclasses, the subclass takes the definition from the left-most class in the superclass list.
  2. If the class definition contains Inheritance = right, then the subclass takes the definition from the right-most class in the superclass list.
    For reasons of history, most Caché classes contain Inheritance = right.
Primary Superclass
Any class that extends other classes has a single primary superclass.
No matter which inheritance order a class uses, the primary superclass is the first one, reading left to right.
For any class-level compiler keywords, a given class uses the values specified in its primary superclass.
For a persistent class, the primary superclass is especially important; see Classes and Extents,” later in this book.
Most-Specific Type Class
Although an object can be an instance belonging to the extents of more than one class — such as that of various superclasses — it always has a most-specific type class (MSTC). A class is the most specific type of an object when that object is an instance of that class, but is not an instance of any subclass of that class.
Overriding Methods
A class inherits methods (both class and instance methods) from its superclass or superclasses, which you can override. If you do so, you must ensure that the signature in your method definition matches the signature of the method you are overriding. This even includes that any argument of a subclass’s method cannot have a data type specified if the matching argument of the superclass’s method has no data type specified. The method in the subclass can, however, specify additional arguments that are not defined in the superclass.
Within a method in a subclass, you can refer to the method that it overrides in a superclass. To do so, use the ##super() syntax. For example:
//overrides method inherited from a superclass
Method MyMethod() 
{
  //execute MyMethod as implemented in the superclass
  do ##super()
  //do more things....
}
Note:
##super is not case-sensitive.
For More Information
For more information on the topics covered in this chapter, see the following books: