Skip to main content

Caché Classes

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:

generated description: packages as folders

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:

generated description: packages in project

This book does not discuss projects in any detail; for information, see Using 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:

  • Parameters — A parameter defines a constant value for use by this class. The value is set at compilation time.

  • 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 subroutine.

  • Properties — A property contains data for an instance of the class.

  • 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.

  • XData blocks — An XData block is a well-formed XML document within the class, for use by the class.

    These have many possible applications.

  • Other kinds of class members that are relevant only for persistent classes; these are discussed in the next chapter.

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 first line gives the name of the class. MyApp.Main.SampleClass is the full class name, MyApp.Main is the package name, and SampleClass is the short class name.

    Studio and other user interfaces treat each package as a folder. For example:

    generated description: studio packages

  • You cannot edit the class name. (If you do, your changes are ignored, and the original class name is shown when you next open the class definition.) For information on renaming a class, see “Renaming a Class,” later in this book.

  • Extends is a compiler keyword.

    The Extends keyword specifies that this class is a superclass of %RegisteredObjectOpens in a new tab, which is a system class, discussed in the next chapter. This class extends only one class, but it is possible to extend multiple other classes. Those classes, in turn, can extend other classes.

  • CONSTANTMESSAGE is a parameter. By convention, all parameters in Caché system classes have names in all capitals. This is a convenient convention, but you are not required to follow it.

    The Internal keyword is a compiler keyword. It marks this parameter as internal, which suppresses it from display in the class documentation. This parameter has a string value.

  • VariableMessage and MessageCount are properties. The item after As indicates the types for these properties. InitialExpression and Required are compiler keywords.

  • HelloWorld() is a class method and it returns a string; this is indicated by the item after As.

    This method uses the value of the class parameter.

  • WriteIt() is an instance method and it does not return a value.

    This method uses the value of the class parameter and values of two properties.

    The ServerOnly compiler keyword means that this method will not be projected to Java or C++ clients.

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:

  • The reference is within a class, and the referenced class is in the same package or subpackage.

  • The reference is within a class, and the class uses the IMPORT directive to import the package or subpackage that contains the referenced class.

  • The reference is within a method, and the method uses the IMPORT directive to import the package or subpackage that contains the referenced class.

  • You are referring to a class in the %Library package, which is specially handled. You can refer to the class %Library.ClassName as %ClassName. For example, you can refer to %Library.StringOpens in a new tab as %StringOpens in a new tab.

  • You are referring to a class in the User package, which is specially handled. For example, you can refer to User.MyClass as MyClass.

    InterSystems does not provide any classes in the User package, which is reserved for your use.

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:

  • To define a value that should not be changed at runtime.

  • To define user-specific information about a class definition. A class parameter is simply an arbitrary name-value pair; you can use it to store any information you like about a class.

  • To customize the behavior of the various data type classes (such as providing validation information) when used as properties; this is discussed in the next section.

  • To provide parameterized values for method generator methods to use.

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é:

  • Attributes, which hold values. The value can be any of the following:

    • A single, literal value, usually based on a data type.

    • An object value (this includes collection objects and stream objects, both introduced in the next chapter).

    • A multidimensional array. This is less common.

    The word property often refers just to properties that are attributes, rather than properties that hold associations.

  • Relationships, which hold associations between objects.

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:

  • In each definition, the item after As is the type of the property. Each type is a class. The syntax As List Of is shorthand for a specific collection class; these are discussed in the next chapter.

    %StringOpens in a new tab, %DateOpens in a new tab, and %NumericOpens in a new tab are data type classes.

    %StringOpens in a new tab is the default type.

  • Diagnoses is a relationship property; the rest are attribute properties.

  • PatientID, Gender, BirthDate, and Age can contain only simple, literal values.

  • PatientID is required because it uses the Required keyword. This means that you cannot save an object of this class if you do not specify a value for this property.

  • Age is not saved to disk, unlike the other literal properties. This is because it uses the Transient keyword.

  • MyTempArray is a multidimensional property because it uses the MultiDimensional keyword. This property is not saved to disk by default.

  • PrimaryCarePhysician and Allergies are object-valued properties.

  • The Gender property definition includes values for property parameters. These are parameters in the data type class that this property uses.

    This property is restricted to the values M and F. When you display the logical values (as in the Management Portal), you see Male and Female instead. Each data type class provides methods such as LogicalToDisplay().

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:

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.

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 ObjectScript expression (this applies even if the class is written in another language, such as Caché MVBasic).

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.

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.

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.

Specifies that the property is multidimensional. This property is different from other properties as follows:

  • It does not have associated methods (see the following topics).

  • It is ignored when the object is validated or saved.

  • It is not saved to disk, unless your application includes code to save it specifically.

  • It cannot be exposed through ActiveX or Java.

  • It cannot be stored in or exposed through SQL tables.

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.StringOpens in a new tab, %Library.IntegerOpens in a new tab, %Library.NumericOpens in a new tab, %Library.DateOpens in a new tab, %MV.DateOpens in a new tab, and many others. Because the names of classes of the %Library package can be abbreviated, you can abbreviate many of these; for example, %DateOpens in a new tab is an abbreviation for %Library.DateOpens in a new tab.

Each data type class has the following features:

  • It specifies values for compiler keywords. For a property, a compiler keyword can do things like the following:

    • Make the property required

    • Specify an initial value for the property

    • Control how the property is projected to SQL, ODBC, ActiveX, and Java clients

  • It specifies values for parameters that affect the details such as the following:

    • Maximum and minimum allowed logical value for the data type

    • Maximum and minimum number of characters the string can contain

    • Number of digits following the decimal point

    • Whether to truncate the string if it exceeds the maximum number of characters

    • Display format

    • How to escape any special XML or HTML characters

    • Enumerated lists of logical values and display values to use in any user interface

    • Pattern that the string must match (automatically uses the Caché pattern-matching operator)

    • Whether to respect or ignore the UTC time zone when importing or exporting to XML

  • It provides a set of methods to translate literal data among the stored (on disk), logical (in memory), and display formats.

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 %IntegerOpens in a new tab 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 %StringOpens in a new tab 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 %DateOpens in a new tab 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:

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.

You have the choice of implementation language when creating a server-side method in Caché. The options are basic (Caché Basic), cache (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 (ObjectScript).

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 can only be accessed by methods of the class to which it belongs.

  • It does not appear in the InterSystems Class Reference, which is introduced later in “InterSystems Class Reference.”

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:

  • To refer to a parameter, use an expression like this:

     ..#PARAMETERNAME
    

    In classes provided by InterSystems, all parameters are defined in all capitals, by convention, but your code is not required to do this.

  • To refer to another method, use an expression like this:

    ..methodname(arguments)
    

    Note that you cannot use this syntax within a class method to refer to an instance method.

  • (Within an instance method only) To refer to a property of the instance, use an expression like this:

    ..PropertyName
    

    Similarly, to refer to a property of an object-valued property, use an expression like this:

    ..PropertyNameA.PropertyNameB
    

    This is known as Caché dot syntax.

    Also, you can invoke an instance method of an object-valued property. For example:

     do ..PropertyName.MyMethod()
    

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:

  • To invoke a class method and access its return value, use an expression like the following:

    ##class(Package.Class).MethodName(arguments)
    

    For example:

     Set x=##class(Util.Utils).GetToday()

    Or, if you are not interested in the return value, use DO as follows:

     Do ##class(Util.Utils).DumpValues()
    Note:

    ##class is not case-sensitive.

  • To invoke an instance method, create an instance (as described in the next chapter) and then use an expression like the following to invoke the method and access its return value:

    instance.MethodName(arguments)
    

    For example:

     Set x=instance.GetName()

    Or, if you are not interested in the return value, use DO as follows:

     Do instance.InsertItem("abc")

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 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:

  • In Zen pages, you use XData blocks to describe the look and feel of the page.

  • WS-Policy support for Caché web services and web clients. See Creating Web Services and Web Clients in Caché. In this case, an XData block describes the security policy.

  • In DeepSee, you use XData blocks to define cubes, subject areas, KPIs, and other elements.

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:

FeedbackOpens in a new tab