Object variables: Difference between revisions

From m204wiki
Jump to navigation Jump to search
Line 179: Line 179:
In the above example, the class variable '''initTime''' is set
In the above example, the class variable '''initTime''' is set
to the time the instance of the object is created.
to the time the instance of the object is created.
The New constructor is run automatically by the [[Janus SOAP User Language Interface]] when the New
The New constructor is invoked in a program to create a <code>pet</code> instance:
function is invoked in a program:
<pre>
<pre>
%josh  is object pet
%josh  is object pet
Line 203: Line 202:
</pre>
</pre>


Even though, as noted before, an invocation of the New function is not really
The parameters
a direct invocation of the New constructor, the parameters for a New constructor
must be specified on the New constructor:
must be specified on the New function:
<pre>
<pre>
%snoopy  is object pet
%snoopy  is object pet
Line 243: Line 241:


Instead of using a constructor to return a modified object as in the example
Instead of using a constructor to return a modified object as in the example
above, you should consider using a [["factory method"|#Virtual Constructor methods]].
above, you should consider using a [[#Virtual Constructor methods|"factory method"]].


The New function can also be used in these ways:
The <var>New</var> <var>Constructor</var> can also be used in these ways:
<ul>
<ul>
<li>To specify a new instance of a class
<li>To specify a new instance of a class
Line 309: Line 307:


A function that invokes a constructor can be used in any other
A function that invokes a constructor can be used in any other
context in which the New function can be used.
context in which the <var>New</var> <var>Constructor</var> can be used.
<li>It is still valid to invoke the New function even if there
is no constructor called New.
In such a case, there will simply be no constructor invoked.
<li>It is recommended that you always begin constructor names with the
<li>It is recommended that you always begin constructor names with the
word "New" to distinguish them from other methods.
word "New" to distinguish them from other methods.
Line 387: Line 382:
interchangeably, and one can be changed to the other as requirements
interchangeably, and one can be changed to the other as requirements
change, without breaking existing applications.
change, without breaking existing applications.
===Object variables within class definitions===
===Object variables within class definitions===



Revision as of 03:41, 10 July 2011

Object variables

Object variables are used to declare variables that refer to instances of a class. Despite their name, they are not really objects, per se, but references or pointers to objects. The underlying objects themselves are never directly accessible and can only be accessed via an object reference. This concept of reference variables being used to access underlying objects is common to all object-oriented languages. There are two, primary reasons for this:

  1. Objects can get quite large, and moving them around an application rather than moving references around would be extremely expensive.
  2. Objects usually represent some "real world" entity. To have multiple copies of such an entity in an application creates the possibility that these copies would get out of synch, causing application complexity and even bugs.

Assignment and comparison operations are probably the two most significant places to note the distinction between object variables being references to the objects or the objects themselves. The following is an example of assignment of an object reference, rather than an object:

class pet
   public
      variable type  is string len 16
   end public
end class
 ...
%rover     is object pet
%fluffy    is object pet
 ...
%rover:type = 'Dog'
%fluffy = %rover
%fluffy:type = 'Cat'
print %rover:type

Because the statement %fluffy = %rover assigns to %fluffy a reference to the same object as %rover, both variables end up referring to the same object. The statement %fluffy:type = 'Cat' therefore changes the underlying object pointed to by %rover (since it's the same object as the one pointed to by %fluffy). As a result, the Print statement prints "Cat".

As assignment assigns references, comparison compares references. That is, a comparison between two object references does not compare the objects being referred to, but it instead compares whether two object references point to the same object.

Consider the following example::

class pet
   public
      variable type  is string len 16
   end public
end class
 ...
%rover     is object pet
%fluffy    is object pet
 ...
%rover = new
%fluffy = new
%rover:type = 'Dog'
%fluffy:type = 'Dog'
if %fluffy eq %rover then
   print 'They''re equal!'
end if

This will not print "They're equal". Even though the objects pointed to by %fluffy and %rover have the same variable values, they are different objects, so %fluffy is not equal to %rover. However, if the code were changed:

class pet
   public
      variable type  is string len 16
   end public
end class
 ...
%rover     is object pet
%fluffy    is object pet
 ...
%rover:type = 'Dog'
%rover = %fluffy
if %fluffy eq %rover then
   print 'They''re equal!'
end if

"They're equal" will be printed, because %rover = %fluffy sets %rover to point to the same object as %fluffy, making them equal.

note.

Since order comparisons (GE, LE, GT, and LT) are meaningless for references — when is a reference to one object greater than a reference to another? — only equality and inequality (EQ and NE) comparisons are allowed for objects.

enote.

Despite the foregoing remarks, for most purposes, one can still think of object variables as being the objects themselves, rather than references to the object. For example, in the statement:

%rover:type = 'Ferret'

it is quite natural to think of the statement as setting %rover's Type to "ferret," rather than as setting the type of the object referenced by %rover to "Ferret."

Creating object instances

Since object variables are references to objects, two questions naturally arise: what object does an object variable refer to initially, and where do the objects referred to come from? The answer to the first question is that object variables initially point to no objects. So, the following program would cause a request cancellation error:

class pet
   public
      variable type  is string len 16
   end public
end class
 ...
%rover     is object pet
 
%rover:type = 'Dog'

The statement %rover:type = 'Dog' attempts to set the object's type to "Dog," but there is no instance of an object for which to set the type. This kind of error is sometimes referred to as a null pointer exception.

So how, then, is an object created? The answer is via a special method called New.

Using New or other constructors

By default, each non-Abstract class contains a default Constructor, named New (although it is Private if Disallow New is specified in the Public block). A Constructor is not the same as a Function or Subroutine:

  • It operates on (i.e., the value of %this within the Constructor) a newly created instance of the class.
  • The value of %this is returned as the result; like a Subroutine, the Return statement may not specify a value, but like a Function, it does produce a result.

You can invoke the New constructor and assign its result as follows:

%rover = new

The class of the object can be indicated with New as follows:

%rover = %(pet):new

The syntax for invoking a Constructor is the same as the syntax for invoking a Shared method which returns an instance of the class. While specifying the class in this way is unnecessary and might be viewed as detrimental because it results in the class name being sprinkled around inside application code, it might also be viewed as beneficial — making the object class clear in the part of code where the object is created.

Sometimes it is useful to run code when creating an instance of a class. In such cases, a class can explicitly define the New Constructor:

class pet
   public
      variable initTime is float
      constructor new
   end public
   constructor new
      %initTime = $sir_datens
   end constructor
end class

In the above example, the class variable initTime is set to the time the instance of the object is created. The New constructor is invoked in a program to create a pet instance:

%josh  is object pet
 ...
%josh = new

Sometimes it is useful to have a constructor take parameters. For example, if it doesn't make sense to have a Pet object without a Type, make the New constructor require an input parameter that sets the class Type variable:

class pet
   public
      variable type  is string len 16
      constructor new(%type is string len 16)
   end public
   constructor new(%type is string len 16)
      %this:type = %type
   end constructor
end class

The parameters must be specified on the New constructor:

%snoopy  is object pet
 ...
%snoopy = new('Beagle')

As of Sirius Mods Version 7.2, constructors are allowed to change the object being constructed. For example, variable ExistingObj assigns an existing Dachsie object when constructor Newbie is called:

class dachsie
   public
    ...
     constructor newbie
    ...
   end public
 
   public shared
     variable ExistingObj is object dachsie
   end public shared
 
   constructor newbie
     %this = %(this):existingObj
   end constructor
   ...
end class

However, this feature was actually provided for a different purpose, and using it in the context shown above requires caution. While %this is an input parameter to the constructor, changing it changes the object returned to the constructor invoker, so it acts almost like an output parameter. Modifying %this within the constructor can also produce unexpected behavior if the constructor is itself called from an extension class Construct statement.

Instead of using a constructor to return a modified object as in the example above, you should consider using a "factory method".

The New Constructor can also be used in these ways:

  • To specify a new instance of a class as an input parameter to a method:
    class pet
       public
        ...
         subroutine compare(%pet is object pet)
        ...
       end public
      ...
    end class
     ...
    %lassie is object pet
     ...
    %lassie:compare( new('Collie') )
    
  • Without being explicitly specified, to automatically create an instance of an object when an object variable is first referenced. For such instantiation, you must specify Auto New in the object's declaration.

Most object-oriented languages support only a single constructor name for a class. If multiple constructors are needed, this is achieved with overloading — the use of different methods with the same name that are distinguished by their parameter lists. Overloading, however, can be confusing, and it is limited: you can't have multiple methods with the same name and the same datatypes in the parameter list.

The Janus SOAP User Language Interface supports multiple constructors:

class resource
   public
     constructor newFromUrl(%url is string len 255)
     constructor newFromProc(%proc is string len 255)
      ...
   end public
   ...
end class
  ...
%res  is object resource
  ...
%res = newFromUrl('http://nfl.com/score')
  ...
%res = newFromProc('LOCAL.RESOURCE.PROC')
  ...
%res = new
listh

This example illustrates:

  • Constructors not called New are invoked in exactly the same way as the New constructor — by assignment from the name of a function with the same name as the constructor. This function creates an instance of the class, calls the constructor with the same name, then returns a reference to the created instance. A function that invokes a constructor can be used in any other context in which the New Constructor can be used.
  • It is recommended that you always begin constructor names with the word "New" to distinguish them from other methods.

Although this section has discussed only public constructors, that is, constructors defined for use outside the class definition, you may also define private constructors, which may only be invoked from inside the class. You declare such a constructor in the Private block of a class definition, and you define it with your other methods after the declaration blocks.

If you want to define a private constructor named New, however, you must make sure to specify Disallow New in the Public block of the class definition (see :figref refid=bloksyn.). No other private constructors require an additional Disallow New in the Public block.

Virtual Constructor methods

It is possible to have a non-constructor shared function return an instance of a class:

class resource
   public shared
      function newFromUrl(%url is string len 255) -
               is object resource
      ...
   end public shared
 
   function newFromUrl(%url is string len 255) -
            is object resource
       %return is object resource
       print 'In the factory'
       %return = new
   end function
   ...
end class
  ...
%res  is object resource
  ...
%res = %res:newFromUrl('http://nfl.com/score')

Such functions are often called factory methods or virtual constructors. As the example illustrates, two things distinguish factory methods from constructors:

  • They can run code before the object is instantiated.
  • They might not actually instantiate an object but return a reference to an existing instance. Such a method may not truly be a factory method.

The support for multiple constructors means that Janus SOAP User Language Interface users need factory methods less than those using most other object-oriented programming languages.

Virtual constructors can be invoked in much the same way that regular constructors are invoked, that is, without specifying the class name if the class name can be determined from context. For example, the above example of the NewFromUrl virtual constructor can be changed to the following:

%res  is object resource
  ...
%res = newFromUrl('http://nfl.com/score')

Similarly, if a method called Send in class Coworker took a resource object as an input parameter, the NewFromUrl virtual constructor could also be used as follows:

%sufjan   is object coworker
 ...
%sufjan:send(newFromUrl('http://asthmatickitty.com'))

Because the naming rules for virtual constructors are now identical to that for regular constructors, the two can be used interchangeably, and one can be changed to the other as requirements change, without breaking existing applications.

Object variables within class definitions

Classes can, themselves, contain object variables:

class pet
   public
    ...
      variable owner is object human
    ...
   end public
 ...
end class

Remember that an object variable is not really the object itself but a reference to an underlying object so, in the case of an object variable inside a class block, the class does not contain another object, per se, but simply a reference to another object.

It is possible for two different classes to each contain references to each other. But for an object variable to be defined, the object class must already have been declared. So if two classes contain references to each other, at least one must be declared without the references before the other. The simplest way to do this is with an empty Class block:

class human
end class
 
class pet
   public
    ...
      variable owner is object human
    ...
   end public
 ...
end class
 ...
class human
   public
    ...
      variable dog is object pet
    ...
   end public
 ...
end class

It is also possible for a class to contain object variables that refer to the containing class:

class pet
   public
    ...
      variable sibling is object pet
    ...
   end public
 ...
end class

In such a case, no empty Class block is necessary to declare the class because the Class block containing the object variable is sufficient to declare the existence of the class.

Discarding objects

The New function creates an instance of a class. This instance is maintained until it is either implicitly or explicitly discarded (or destroyed in the terminology of some object-oriented languages).

Discarding explicitly

An underlying object can be discarded explicitly by invoking the Discard method (system subroutine) against it:

%mick   is object dog
 ...
%mick = new('Terrier')
 ...
* Done with object pointed to by %mick
%mick:discard

The Discard method never takes any input parameters and returns no value, so it is a Subroutine. The Discard method is an AllowNullObject method because it can be invoked with a null object, such as one that was never set:

%mick   is object dog
 
for %i from 1 to 10
   %mick:discard
   ...
end for

In the first loop iteration, above, %mick would not be set, so the %mick:discard call would not have anything to discard, but it would not indicate a request-cancelling null-pointer error.

Discarding implicitly

Objects may be discarded implicitly, when there are no more references to the underlying object. If there are no references to an underlying object, there is no way to retrieve or set values in the object or to invoke methods against the object. Since there is no reason to maintain the object any more, the Janus SOAP User Language Interface automatically discards the object. The following code demonstrates this principle:

%bunny   is object dog
  ...
%bunny = new('Ibizan Hound')
%bunny:show
%bunny:feed
%bunny = new('Yorkshire Terrier')

When the statement %bunny = new('Yorkshire terrier') is executed, a new object is created, but it is no longer possible to access the object instantiated by %bunny = new('Ibizan hound') — there are no longer any references to that object. Because of that, the first Dog object is automatically discarded when the second Dog object is created and referenced by %bunny. If a second reference to that object had been set, it would not have been discarded:

%bunny   is object dog
%dallas  is object dog
  ...
%bunny = new('German Shepherd')
%dallas = %bunny
%bunny:show
%bunny:feed
%bunny = new('Yorkshire terrier')

A reference to an object can be inside another object:

class dog
   public
      variable name  is string len 32
      variable breed is string len 32
      variable nextCompetitor is object
      ...
   end public
end class
 ...
%first   is object dog
 ...
%first = new('Josh', 'Newfoundland')
%first:nextCompetitor = new('Mick', 'Kerry Blue Terrier')

In the above example, the object created by new('Mick', 'Kerry Blue Terrier') can be accessed via %first:nextCompetitor, so it will not be implicitly discarded. In such a way, it is possible to build a chain of objects:

%first   is object dog
%last    is object dog
%new     is object dog
 ...
%new   = new('Josh', 'Newfoundland')
%first = %new
%last  = %new
%new   = new('Mick', 'Kerry Blue Terrier')
%last:nextCompetitor = %new
%last  = %new
%new   = new('Les', 'Pekingese')
%last:nextCompetitor = %new
%last  = %new
%new   = new('Bunny', 'Ibizan Hound')
%last:nextCompetitor = %new
%last  = %new

In the above example, the object created by new('Les', 'Pekingese') can be accessed by %first:nextCompetitor:nextCompetitor, so it is not implicitly discarded.

By linking objects in such a way, it is possible to create a wide variety of object super-structures, including chains, trees, webs, and cycles. A cycle is a collection of objects where the objects are linked in such a way as to form a ring:

%first   is object dog
%last    is object dog
%new     is object dog
 ...
%new   = new('Josh', 'Newfoundland')
%first = %new
%last  = %new
%new   = new('Mick', 'Kerry Blue Terrier')
%last:nextCompetitor = %new
%last  = %new
%new   = new('Les', 'Pekingese')
%last:nextCompetitor = %first

A cycle can even be created with a single object:

%new     is object dog
 ...
%new   = new('Josh', 'Newfoundland')
%new:nextCompetitor = %new

A web of objects can contain many cycles of objects.

If a cycle of objects is created, each object in the cycle will always have at least one reference to it — the previous item in the cycle. This means that even if all other references to the objects in a cycle were eliminated, the objects would still not be discarded because they would each still have a reference to them, even though those references, themselves, aren't reachable from any variables in the request. The following illustrates a case where two objects cannot be reached via any %variable, but they won't be discarded because they reference each other:

%first   is object dog
%last    is object dog
%new     is object dog
 ...
%first = new('Josh', 'Newfoundland')
%new   = new('Mick', 'Kerry Blue Terrier')
%first:nextCompetitor = %new
%new:nextCompetitor   = %first
%new   = new('Les', 'Pekingese')
%first = %new

Such objects will not be discarded by the Janus SOAP User Language Interface until the user logs off. Most object-oriented languages solve this problem with garbage collection — a process in which all objects are scanned to determine if they are "reachable" from some base variables (in the Janus SOAP User Language Interface these would be local and common %variables).

Janus SOAP User Language Interface has a DeepDiscard method, which provides an alternative approach to some of these cases that is less expensive than garbage collection and simpler than Discard. DeepDiscard is discussed in the next section.

Deep discard of objects

The Discard method (Object class subroutine) is usually available to explicitly discard the object referenced by an object variable. If an object contains a reference to another object (of the same or a different class), an explicit Discard results in the elimination of the reference. This removal of the reference can result in an implicit discard of the reference object, as shown in the following example.

The object referenced by %chain contains a reference to another object. When %chain is discarded, the reference to the object with the name "Frack" is lost. Since this is the only reference to that object, the object is then implicitly discarded.

class linkedList
   public
      variable   name is string len 32
      variable   next is object linkedList
   end public
end class
 ...
%chain     is object linkedList
%chain = new
 
%chain:name = 'Frick'
%chain:next = new
%chain:next:name = 'Frack'
 
%chain:discard

Now suppose the class definition remains the same, but the code is changed to the following:

%chain     is object linkedList
%link      is object linkedList
 
%chain = new
%chain:name = 'Frick'
%chain:next = new
%chain:next:name = 'Frack'
%link = %chain:next
 
%chain:discard

In this case, even when the object referenced by %chain is discarded, and its reference to the "Frack" object goes away, there is still a reference to the "Frack" object, namely, %link, so that object is not discarded.

Janus SOAP User Language Interface also has a generic DeepDiscard method:

%obj:DeepDiscard

Like the Discard method, DeepDiscard explicitly discards the method object. But, unlike Discard, it also explicitly discards all objects that are referenced directly or indirectly (a reference to an object that contains a reference to another object, and so on) by the object being discarded. This makes DeepDiscard useful for discarding an entire chain of objects, an object tree, an object cycle, or even an object forest, even if there are still references to some of the objects in the request.

Since DeepDiscard cleans up an object cycle or forest, it is an efficient alternative to having garbage collection clean these up, if there is an obvious point in code where the object cycle or forest is no longer used.

Just as with the Discard method, there is a danger with DeepDiscard that some reference to an explicitly discarded object might still be required. But because it has a potential to discard more objects, the risk is somewhat greater with DeepDiscard, so it should be used with caution.

The DeepDiscard method cannot be used on any object in a class that

  • is declared with a Disallow Discard clause
  • contains a reference to a class defined with Disallow Discard
  • contains an indirect reference to a class that is defined with Disallow Discard

Without these rules, an explicit DeepDiscard an object could result in the explicit discard of an object of a class that expressly disallows explicit discards.

For most system classes, DeepDiscard works identically to Discard. The exceptions to this are:

  • XmlNode objects, which you may DeepDiscard but not Discard. If you DeepDiscard an XmlNode, the underlying XmlDoc object is also discarded.
  • XmlNodelist objects, which you may Discard or DeepDiscard. If you Discard an XmlNodelist, the XmlNodelist object (only) is discarded. If you DeepDiscard an XmlNodelist, both the XmlNodelist object and the underlying XmlDoc object are discarded.

Working with null valued object variables

If an object variable does not refer to an object, either because it was never set, or because the object to which it refered was discarded, it is considered to have a Null value. While, an object variable with a null value cannot be used as the object for most methods (except those declared with AllowNullObject), they can be used in comparisons.

The most common comparison done with objects, in fact, is to test whether the object is null:

%first   is object dog
%new     is object dog
 ...
%new   = new('Josh', 'Newfoundland')
if %first eq null then
   %first = new
else
   %last:nextCompetitor = %new
end of
%last  = new

If a null object variable is compared with another object variable, it is considered equal if, and only if, the other object variable is null.

A null object variable can be assigned to another object variable:

%first   is object dog
%new     is object dog
 
%new   = %first

And a variable can be explicitly set to Null:

%winner  is object dog
 ...
%winner = null

In both comparisons and assignments, Null is actually a shorthand for a shared null property on the class, and it can be indicated as such:

%winner  is object dog
 ...
%winner = %(dog):null
 ...
if %winner eq %(dog):null
 ...

The value Null can also be passed as an input parameter to a method:

class show
   public
      ...
      function score(%entrant is object dog allowNull) -
               is float
      ...
   end public
   ...
end class
 ...
%winner  is object dog
%akc     is object show
 ...
%minScore = %akc:score(null)
 ...
note.

Assigning a value to an object property, that is to a Property that returns an object, is a special case of passing an input parameter to a method. The value assigned is an implicit parameter of the Set method of the property. In such cases, you may validly assign a Null value, as if the implicit parameter included an implicit AllowNull keyword.

enote.

An object that is explicitly discarded causes all references to that object to become null:

%first   is object dog
%new     is object dog
 
%new   = new('Dallas', 'German Shepherd')
%first = %new
%first:discard

In the above example, both %first and %new are set to Null after the %first:discard.

Passing object variables as parameters

Object variables can be passed as method or complex subroutine parameters:

class clown
   public
      subroutine honk(%nose is object nose)
      ...
   end public
   ...
end class
 ...
%bozo    is object clown
%schnoz  is object nose
 ...
%bozo:honk(%schnoz)

By default, passing a null object variable as a method or complex subroutine parameter causes request cancellation. However, the AllowNull keyword can be specified as an Input parameter qualifier in the method or complex subroutine declaration to indicate that the method or complex subroutine will accept null objects:

class clown
   public
      subroutine honk(%nose is object nose allowNull)
      ...
   end public
   ...
end class

An Output object variable is always allowed to be null and can be set by a method:

class clown
   public
      subroutine honk(%nose is object nose output)
      ...
   end public
   subroutine honk(%nose is object nose output)
      %nose = new('red')
   end subroutine
   ...
end class
 ...
%bozo    is object clown
%schnoz  is object nose
 ...
%bozo = new
%bozo:honk(%schnoz)

In this example %schnoz would be set to reference a new instance of a Nose object that was instantiated by the Honk method.

An Input object variable is passed by value to a method and may be modified within the method. The method actually receives a copy of a pointer to the object. The copied pointer may be used to modify the referenced object, and the modifications are propagated to the object outside the subroutine. Changes to the pointer like assignment or new instantiation, are allowed, but they are not propagated to the outer object.

In the example below, the subroutine modifies the %schnoz object color but the reassignment to %schnozola does not affect %schnoz:

class clown
public
subroutine honk(%nose is object nose output)
 ...
property color is string
 ...
end public
 
subroutine honk(%nose is object nose output)
%schnozola is object nose
If %clown eq %bozo
  'red' = %nose:color
 ...
%nose = %schnozola
 ...
end subroutine
 ...
property color is string
 ...
end property color
end class
 ...
%bozo is object clown
%schnoz is object nose
 ...
%bozo = new
%bozo:honk(%schnoz)
note

A case where a method significantly affects the object referenced by an Input object variable is the Discard method. A Discard method invoked against an Input parameter sets the Input parameter and call argument to Null.

enote.

. This can happen directly:

subroutine honk  (%nose is object nose input)
 
   %nose:discard
 
end subroutine

Or, it can happen indirectly:

subroutine honk  (%nose is object nose input)
 
   %beak is object nose
 
   %beak = %nose
   %beak:discard
 
end subroutine