Inheritance and polymorphism: Difference between revisions

From m204wiki
Jump to navigation Jump to search
mNo edit summary
 
(19 intermediate revisions by 5 users not shown)
Line 1: Line 1:
Support for inheritance and polymorphism was added to <var class="product">[[Janus SOAP User Language Interface|Janus SOAP ULI]]</var> in
<var class="product">Sirius Mods</var> version 6.7.
==Background==
==Background==
Inheritance and polymorphism are two closely related but subtly different
Inheritance and polymorphism are two closely related but subtly different
Line 29: Line 25:
   
   
As this example illustrates, it is possible to extend many system classes
As this example illustrates, it is possible to extend many system classes
as well as <var class="product">User Language</var> classes.
as well as user-defined classes.
There are exceptions, however.
There are exceptions, however:
It is not possible to extend the following types of system classes:
<ul>
<li>It is not possible to extend the following types of system classes:
<ul>
<ul>
<li>[[Collections]]
<li>[[Collections]]
Line 38: Line 35:
</ul>
</ul>
   
   
Before <var class="product">Sirius Mods</var> 7.0, it was also not possible to extend
<li>It is possible to extend a file class in a specific context.
file classes, such as the <var>Recordset</var> class, the <var>Record</var> class, and the
<var>RecordsetCursor</var> class.
Under <var class="product">Sirius Mods</var> 7.0 and later, it is possible to extend a file class
in a specific context.
For example, the following <code>Class</code> statement extends the <var>Recordset</var> class
For example, the following <code>Class</code> statement extends the <var>Recordset</var> class
for file <code>FOOBAR</code>:
for file <code>FOOBAR</code>:
Line 48: Line 41:
</p>
</p>
   
   
It is not possible to generically extend any file classes.
<li>It is not possible to generically extend any file classes.
For example, the following is '''not''' allowed:
For example, the following is '''not''' allowed:
<p class="code">class myRecordset extends recordset
<p class="code">class myRecordset extends recordset
</p>
</p>
</ul>
   
   
When one class extends another class, members of the base class can
When one class extends another class, members of the base class can
Line 199: Line 193:
because the member name did not exist in the base class.
because the member name did not exist in the base class.
</ul>
</ul>
==Multiple Inheritance==
 
<var class="product">Janus SOAP ULI</var> allows a class to extend more than a single class.
==Multiple inheritance==
<var class="product">SOUL</var> allows a class to extend more than a single class.
This seems reasonable, as many objects in the real world can be thought of
This seems reasonable, as many objects in the real world can be thought of
as extensions to more than one base class.
as extensions to more than one base class.
Line 208: Line 203:
class and to a Property class.
class and to a Property class.
   
   
To extend multiple classes with <var class="product">Janus SOAP ULI</var>, you separate the extended classes
To extend multiple classes with <var class="product">SOUL</var>, you separate the extended classes
with the <var>And</var> keyword in the <var>Class</var> statement <var>Extends</var> clause:
with the <var>And</var> keyword in the <var>Class</var> statement <var>Extends</var> clause:
<p class="code">class pet extends animal and property
<p class="code">class pet extends animal and property
Line 295: Line 290:
but start to work differently, possibly producing subtle and difficult
but start to work differently, possibly producing subtle and difficult
to diagnose problems.
to diagnose problems.
==Repeat inheritance==
==Repeat inheritance==
With multiple inheritance, it is possible to define an extension class
With multiple inheritance, it is possible to define an extension class
Line 306: Line 302:
class, and once via the AmericanDriver class.
class, and once via the AmericanDriver class.
This is an example of '''repeat inheritance''' and was <i><b>not</b></i>
This is an example of '''repeat inheritance''' and was <i><b>not</b></i>
allowed by the <var class="product">Janus SOAP ULI</var> before <var class="product">Sirius Mods</var> version 7.8.
allowed by <var class="product">SOUL</var> until support was introduced in <var class="product">Sirius Mods</var> version 7.8.
<var class="product">Sirius Mods</var> version 7.8, however, <i><b>does</b></i> support repeat inheritance.
   
   
While the past lack of support for repeat inheritance might
While the past lack of support for repeat inheritance might
Line 330: Line 325:
   
   
Still, there might be some special cases where repeat inheritance does
Still, there might be some special cases where repeat inheritance does
make sense so, as of <var class="product">Sirius Mods</var> version 7.8, <var class="product">Janus SOAP ULI</var> supports repeat inheritance,
make sense so <var class="product">SOUL</var> supports repeat inheritance,
with some restrictions.
with some restrictions.
The chief restriction on repeat inheritance is that if a class is
The chief restriction on repeat inheritance is that if a class is
Line 481: Line 476:
%americanMale:birthYear = 1992
%americanMale:birthYear = 1992
</p>
</p>
==Interfaces==
==Interfaces==
Multiple inheritance provides all the capabilities that interfaces
Multiple inheritance provides all the capabilities that interfaces
Line 487: Line 483:
are abstract (and so must be overridden) and which contains no
are abstract (and so must be overridden) and which contains no
variables.
variables.
In <var class="product">Janus SOAP ULI</var>, it is easy enough to define an abstract class that has
In <var class="product">SOUL</var>, it is easy enough to define an abstract class that has
no variables and in which all methods are abstract.
no variables and in which all methods are abstract.
This pretty much provides the exact same functionality as interfaces,
This pretty much provides the exact same functionality as interfaces,
meaning that there is really no compelling reason to have support for
meaning that there is really no compelling reason to have support for
special interface classes in <var class="product">Janus SOAP ULI</var>.
special interface classes in <var class="product">SOUL</var>.
   
   
The main reason for the existence of interfaces in other languages
The main reason for the existence of interfaces in other languages
Line 501: Line 497:
that arise with support for true multiple inheritance.
that arise with support for true multiple inheritance.
And, while one might argue that these restrictions are a good thing,
And, while one might argue that these restrictions are a good thing,
there is nothing in <var class="product">Janus SOAP ULI</var> that prevents one from adhering to
there is nothing in <var class="product">SOUL</var> that prevents one from adhering to
these restrictions by choice.
these restrictions by choice.
   
   
On the other hand, it is also conceivable that some of the restrictions
On the other hand, it is also conceivable that some of the restrictions
on interfaces could cause problems.
on interfaces could cause problems.
For example, the fact that all methods in an interface must be abstract
For example:
<ul>
<li>The fact that all methods in an interface must be abstract
means that if one wants to add a method to an interface, all extenders of
means that if one wants to add a method to an interface, all extenders of
that interface must be updated to provide an implementation of that new
that interface must be updated to provide an implementation of that new
method.
method.
Obviously, this is likely to make interfaces somewhat brittle.
Obviously, this is likely to make interfaces somewhat brittle.
With <var class="product">Janus SOAP ULI</var>, you can use an abstract class with no data exactly as
<p>
With <var class="product">SOUL</var>, you can use an abstract class with no data exactly as
an interface. But if you want to add a method, you can add a default
an interface. But if you want to add a method, you can add a default
implementation for that method and, so, make it overridable, not abstract.
implementation for that method and make it overridable, not abstract.
This means that methods can easily be added to interface-like classes
This means that methods can easily be added to interface-like classes
in <var class="product">Janus SOAP ULI</var> without creating a rippling effect that requires updating of
in <var class="product">SOUL</var> without creating a rippling effect that requires updating of
all extenders of the class.
all extenders of the class.
The absence of non-abstract methods in interfaces seems like a less
important restriction than the absence of instance variables in the
<li>The absence of non-abstract methods in interfaces seems like a less
important restriction than the absence of instance variables in
avoiding the semantic and implementation issues brought about by
avoiding the semantic and implementation issues brought about by
multiple inheritance.
multiple inheritance.
Line 524: Line 524:
dropped some day in languages that support interfaces but not multiple
dropped some day in languages that support interfaces but not multiple
inheritance.
inheritance.
</p>
The absence of instance-specific variable support for interfaces can
 
also be a problem, however.
<li>The absence of instance-specific variable support for interfaces can
also be a problem.
<p>
For example, considered a ScheduledObject class where one can use
For example, considered a ScheduledObject class where one can use
a generic queue manager to schedule activities on the object.
a generic queue manager to schedule activities on the object.
Line 535: Line 537:
But, it would also seem that the ScheduledObject class would really
But, it would also seem that the ScheduledObject class would really
need instance-specific variables such as queue pointers, status flags,
need instance-specific variables such as queue pointers, status flags,
priorities, time stamps and the like.
priorities, time stamps and the like.</p>
<p>
Of course, this is generally not a problem.
Of course, this is generally not a problem.
Any class that wants to be a ScheduledObject would simply extend the
Any class that wants to be a ScheduledObject would simply extend the
Line 542: Line 544:
However, it's not inconceivable that one might have a class that
However, it's not inconceivable that one might have a class that
might sometimes be used as a ScheduledObject, and sometimes not.
might sometimes be used as a ScheduledObject, and sometimes not.
<p>
For example, one might have a Payment class that is sometimes
For example, one might have a Payment class that is sometimes
used for display purposes.
used for display purposes.
Line 558: Line 561:
While this means that Payment objects always carry around
While this means that Payment objects always carry around
the baggage of the ScheduledRequest class, it's probably an acceptable
the baggage of the ScheduledRequest class, it's probably an acceptable
solution in most cases.
solution in most cases.</p>
<p>
It becomes problematic, however, if there's yet another class
It becomes problematic, however, if there's yet another class
that Payment needs to extend like, say, a BusinessTransaction class.
that Payment needs to extend like, say, a BusinessTransaction class.
That class would likely have its own variables and so, again, would
That class would likely have its own variables and so, again, would
not lend itself to being implemented as an interface.
not lend itself to being implemented as an interface.</p>
</ul>
All this said, this amount of inheritance is not common in business
All this said, a large amount of inheritance is not common in business
applications and in the vast majority of classes, neither interfaces
applications, and in the vast majority of classes, neither interfaces
nor multiple inheritance would be necessary.
nor multiple inheritance would be necessary.
However, in the odd cases where they are, multiple inheritance provides
However, in the odd cases where they are, multiple inheritance provides
everything interfaces do and then some.
everything interfaces do and then some.
For this reason, and because <var class="product">Janus SOAP ULI</var> supports multiple inheritance,
For this reason, and because <var class="product">SOUL</var> supports multiple inheritance,
there is no need for <var class="product">Janus SOAP ULI</var> to also provide support for interfaces.
there is no need for <var class="product">SOUL</var> to also provide support for interfaces.
 
==Inheritance and constructors==
==Inheritance and constructors==
An extension class might or might not have any <var>[[Janus SOAP User Language Interface#constructor|constructors]]</var>.
An extension class might or might not have any <var>[[Object oriented programming in SOUL#constructor|constructors]]</var>.
If an extension class has no constructors, the default (<var>[[Object variables#Using New or other Constructors|New]]</var>) constructors for the base classes are called in sequence with no parameters.
If an extension class has no constructors, the default (<var>[[Object variables#Using New or other Constructors|New]]</var>) constructors for the base classes are called in sequence with no parameters.
However, if any of the base class constructors require parameters or
However, if any of the base class constructors require parameters or
Line 640: Line 643:
end constructor
end constructor
</p>
</p>
===Rules for constructors and Inheritance===
There are three basic inheritance-specific rules for constructors:
A perhaps somewhat arbitrary rule for constructors that is enforced at compile time is that a <var>Construct</var> statement must be physically placed inside a constructor for an extension class of the base class being constructed.
 
In addition to that rule are four major inheritance-specific rules for constructors:
<ul>
<ul>
<li>It is invalid to refer to a base class member until the constructor
<li>It is invalid to refer to a base class member until the constructor
for that base class has been entered, that is, until a <var>Construct</var>
for that base class has been entered, that is, until a <var>Construct</var>
statement has been issued for that base class.
statement has been issued for that base class.
<li>It is invalid to invoke multiple <var>Construct</var> statements against the
<li>It is invalid to invoke multiple <var>Construct</var> statements against the
same base class for the same object instance.
same base class for the same object instance.
<li>When exiting from an extension class constructor, <var>Construct</var>
<li>When exiting from an extension class constructor, <var>Construct</var>
statements must have been issued for all base classes for the object.
statements must have been issued for all base classes for the object.
<li>The <code>%this</code> variable cannot be changed by assignment ''within a base class constructor''.
Because an extension class constructor that does a <var>Construct</var> has its own implicit or explicit <code>%this</code> to reference the extension object being constructed, assigning to <code>%this</code> in the base class constructor cannot be allowed
to have an effect on the extension class.
<p>
For example, a base class constructor might use <code>%this = null</code> to signal a problem in the base object construction. An alternative to using <code>%this=null</code> in this case is to use <code>%this:discard</code>. Another alternative is to [[#Throwing exceptions|throw an exception]]. A <var>Throw</var> statement that executes within a constructor causes an automatic
<var>Discard</var> of the object being constructed. </p>
</ul>
</ul>
 
Before <var class="product">Sirius Mods</var> 7.1, these rules were enforced at compile-time.
All violations of these rules are caught at run-time.
The benefit of this was that a program that compiled successfully
This allows more flexibility in writing constructors at the cost of the compile-time checks for rules conformance.
was known to follow the rules.
 
Unfortunately, to make it practical for the compiler to ensure that
the inheritance-specific constructor rules were being followed, a
variety of other restrictions had to be added to constructors.
In practice, some of these restrictions proved to be impediments to
solving certain problems.
As a result, <var class="product">Sirius Mods</var> 7.1 removed almost all of these restrictions
and changed the checks for rules violations to run-time checks.
This allowed more flexibility in writing constructors but, unfortunately,
eliminated the compile-time checks for rules conformance.
The one perhaps somewhat arbitrary rule that still remains is that
a <var>Construct</var> statement must be physically placed inside a constructor.
The following subsections describe the restrictions concerning
extension class constructors.
Because the inheritance-specific constructor rules are enforced at
run-time in <var class="product">Sirius Mods</var> 7.1 and later, these rules do not apply to
those releases.
For earlier releases,
while the rules for extension class constructors might seem complex,
they probably will not come into play outside of fairly complicated
constructors.
Furthermore, any violation of the rules were detected at compile-time
with a clear error message indicating the problem.
Consequently, it is recommended that not too much time be spent learning
these rules until they actually come into play.
Any program that followed the compile-time enforced rules set out in
the following sections under <var class="product">Sirius Mods</var> release before 7.1 are guaranteed
to meet the run-time tests introduced under <var class="product">Sirius Mods</var> 7.1.
This ensures that there are no backward compatibility issues &mdash;
any constructor that worked before <var class="product">Sirius Mods</var> 7.1 would continue to
work under <var class="product">Sirius Mods</var> 7.1 and later.
===Restrictions on the placement of Construct statements===
In <var class="product">Sirius Mods</var> releases before 7.1
there were certain restrictions on statements in an extension
class constructor.
These restrictions were intended to ensure that exactly one <var>Construct</var>
statement was executed for each base class in an extension class constructor:
These restrictions were:
<ul>
<li>Only one <var>Construct</var> statement was allowed per base class, unless the
statements are in mutually exclusive <var>If</var>, <var>ElseIf</var>, or <var>Else</var> clauses.
<li>No labels were allowed before a <var>Construct</var> statement.
<li>No <var>Jump</var> statements were allowed before a <var>Construct</var> statement.
<li><var>Construct</var> statements could not be inside of <var>For</var> or <var>Repeat</var> loops.
<li>If a <var>Construct</var> statement for a base class appeared in an <var>If</var>, <var>ElseIf</var>,
or <var>Else</var> clause, <var>Construct</var> statements for that base class must also have been
present in every other <var>If</var>, <var>ElseIf</var>, and <var>Else</var> clause for that block, and
there must be an <var>Else</var> clause for that block.
</ul>
These restrictions were validated at compile-time, so if a constructor
compiled without errors, it must have satisfied these conditions.
For example, the following definition would result in a compilation error, because
there was no <var>Else</var> clause for the <var>If</var> statement, so the <var>Construct</var> statement
for the <code>Bar</code> class might never be run:
<p class="code">constructor new(%height is float)
  if %height lt 10 then
      construct %(bar):new(10)
  end if
  construct %(foo):newFoo
end constructor
</p>
The following definition would also result in a compilation error, because
the <var>ElseIf</var> clause for the <var>If</var> statement had no <var>Construct</var> statement
for the <code>Bar</code> class:
<p class="code">constructor new(%height is float)
  if %height lt 10 then
      construct %(bar):new(10)
  elseIf %height gt 200 then
      print 'Why bother?'
  else
      construct %(bar):new(%height)
  end if
  construct %(foo):newFoo
end constructor
</p>
The following definition would compile without errors:
<p class="code">constructor new(%height is float)
  if %height lt 10 then
      construct %(bar):new(10)
  elseIf %height gt 200 then
      construct %(bar):new(200)
  else
      construct %(bar):new(%height)
  end if
  construct %(foo):newFoo
end constructor
</p>
===Restrictions on references to base or extension class members===
In addition to restrictions on the placement of <var>Construct</var> statements,
in an extension class constructor,
in <var class="product">Sirius Mods</var> releases before 7.1 there were also some limitations
on references to base or extension class members inside the extension
class constructor.
These restrictions were intended to prevent references to members of
classes that are &ldquo;incomplete&rdquo; because the constructor had not
been run for the object yet.
The restrictions were:
<ul>
<li>No references could be made to a base class member before the
<var>Construct</var> statement for the base class.
<li>No references could be made to extension class members before
all <var>Construct</var> statements for all base classes.
</ul>
For example, suppose the <code>Bar</code> class had a member called <code>Mitzvah</code>.
The following definition would result in a compilation error, because there
was a reference to a <code>Bar</code> class member before the <var>Construct</var>
statement for the <code>Bar</code> class;
<p class="code">constructor new(%height is float)
  print 'Mitzvah = ' with %this:(bar)mitzvah
  construct %(bar):new(%height)
  construct %(foo):newFoo
end constructor
</p>
But the following definition was valid, because the <code>Mitzvah</code> reference came
after the <var>Construct</var> for the <code>Bar</code> class:
<p class="code">constructor new(%height is float)
  construct %(bar):new(%height)
  print 'Mitzvah = ' with %this:(bar)mitzvah
  construct %(foo):newFoo
end constructor
</p>
If the <code>FooBar</code> class had a member called <code>SchmooBar</code>, the following definition was
not valid, because the reference to that member occurred before the
<var>Construct</var> for the <code>Bar</code> class:
<p class="code">constructor new(%height is float)
  construct %(foo):newFoo
  if %height lt 10 then
      print 'SchmooBar = ' with %this:SchmooBar
      construct %(bar):new(10)
  else
      construct %(bar):new(%height)
  end if
end constructor
</p>
However, the following was perfectly valid, because the <var>Construct</var> for both
the <code>Foo</code> and <code>Bar</code> classes would occur before the <code>SchmooBar</code> reference:
<p class="code">constructor new(%height is float)
  construct %(foo):newFoo
  if %height lt 10 then
      construct %(bar):new(10)
      print 'SchmooBar = ' with %this:SchmooBar
  else
      construct %(bar):new(%height)
  end if
end constructor
</p>
The <var>Construct</var> statement in the <var>Else</var> clause, above, was not
a problem, since it would never be executed for the same object for
which the <code>SchmooBar</code> reference is made.
All the member
reference restrictions were detected at compile-time, so
if an extension
class constructor compiled successfully, you could be sure that all the rules
have been observed.
===Restrictions on %this variable assignments===
In <var class="product">Sirius Mods</var> releases before 7.1,
the final restrictions on extension class constructors were that the
<code>%this</code> variable could not be assigned to a variable that references
<ul>
<li>a base class member, unless the <var>Construct</var> statement for the base
class had been issued.
If allowed, this would be a polymorphic assignment (as described in the
next section).
<li>an extension class member, unless all <var>Construct</var> statements for
all base classes had been issued.
</ul>
So, the following assignment to <code>%myFoo</code> was invalid, because it occurred
before the <var>Construct</var> for the <code>Foo</code> class:
<p class="code">constructor new(%height is float)
  %myFoo  is object foo
  construct %(bar):new(%height)
  %myFoo = %this
  construct %(foo):newFoo
end constructor
</p>
However, the following assignment to <code>%myBar</code> was valid, because it occurred
after the <var>Construct</var> for the <code>Bar</code> class:
<p class="code">constructor new(%height is float)
  %myBar  is object bar
  construct %(bar):new(%height)
  %myBar = %this
  construct %(foo):newFoo
end constructor
</p>
The following assignment to <code>%myFooBar</code> was invalid, because it occurred
before the <var>Construct</var> for the <code>Foo</code> class (all base classes had to be constructed
before assignment from <code>%this</code> to a variable of the extension class):
<p class="code">constructor new(%height is float)
  %myFooBar is object fooBar
  construct %(bar):new(%height)
  %myFooBar = %this
  construct %(foo):newFoo
end constructor
</p>
However, the following assignment to <code>%myFooBar</code> was valid, because it occurred
after the <var>Construct</var> for all of <code>FooBar</code>'s base classes:
<p class="code">constructor new(%height is float)
  %myFooBar is object fooBar
  construct %(bar):new(%height)
  construct %(foo):newFoo
  %myFooBar = %this
end constructor
</p>
All <code>%this</code> variable assignment
restrictions were detected at compile-time, so if an extension
class constructor compiled successfully, you could be sure that all the rules
had been observed.
<br>'''Note:'''
A different but related restriction on <code>%this</code> assignments concerns
base class constructors called by <var>Construct</var> statements.
As of <var class="product">Sirius Mods</var> version 7.3, the request is canceled if <code>%this</code> is changed
by assignment ''within a base class constructor''.
Because an extension class constructor that does a <var>Construct</var> has its own
implicit or explicit <code>%this</code> to reference the extension object being constructed,
assigning to <code>%this</code> in the base class constructor cannot be allowed
to have an effect on the extension class.
For example, a base class constructor might
use <code>%this = null</code> to signal a problem in the base object construction.
Prior to version 7.3,
if this constructor were called via <var>Construct</var> and <code>%this</code> were set to null, this
construction problem would be ignored and the program would proceed unknowingly.
An alternative to using <code>%this=null</code> in this case is
to use <code>%this:discard</code>.
Another alternative is to [[#Throwing exceptions|throw an exception]].
A <var>Throw</var> statement that executes within a constructor causes an automatic
<var>Discard</var> of the object being constructed.
==Polymorphism==
==Polymorphism==
Polymorphism is the ability of an object variable
Polymorphism is the ability of an object variable
Line 990: Line 752:
class to a more general class, hence a widening assignment.
class to a more general class, hence a widening assignment.
   
   
Some object-oriented languages also allow what are called '''narrowing assignments'''
Like many other object-oriented languages, <var class="product">SOUL</var> also allows what are called '''narrowing assignments'''
&mdash; assignments from a base class variable to an extension class
&mdash; assignments from a base class variable to an extension class variable.
variable.
Because narrowing assignments are usually not recommended, special syntax is required in SOUL to perform them.  
Before &sirmods. version 6.9,
<var class="product">Janus SOAP ULI</var> did ''not'' allow such assignments.
(For more information about the support that was added for such assignments in <var class="product">Sirius Mods</var>
Version 6.9, see [[#Narrowing assignments and class tests|"Narrowing assignments and class tests"]].
That is, the following assignment from <code>%fuzzy</code> to <code>%tarka</code> is <i><b>not allowed</b></i> and
That is, the following assignment from <code>%fuzzy</code> to <code>%tarka</code> is <i><b>not allowed</b></i> and
results in a compilation error:
results in a compilation error:
Line 1,024: Line 782:
determined at run-time.
determined at run-time.
   
   
Languages that allow narrowing assignments simply compile the
Many languages that allow narrowing assignments simply compile the assignments, and if the assignment fails, produce a run-time
assignments, and if the assignment fails, produce a run-time
error. Such languages make it the programmer's job to write code in such a way as to avoid such errors. Other languages that allow narrowing assignments, require the assignments to use a special syntax for the narrowing assignments to make it clear that the programmer meant to do what is being done, and to make it clear to anyone reading the code that something "funny" is going on.
error.
Such languages make it
the programmer's job to write code in such a way as to
avoid such errors.
Many languages that allow narrowing assignments, require the assignments
to use a special syntax for the narrowing assignments to make it clear
that the programmer meant to do what is being done, and to make it clear
to anyone reading the code that something "funny" is going on.
   
   
Unfortunately, in the case of narrowing assignments, almost anything
Unfortunately, in the case of narrowing assignments, almost anything a programmer is likely to do to prevent narrowing assignment errors is likely to have bad code maintainability implications. To understand why, it is worth continuing the Otter and Mammal
a programmer is likely to do to prevent narrowing assignment errors
is likely to have bad code maintainability implications.
To understand why, it is worth continuing the Otter and Mammal
example.
example.
   
   
Line 1,053: Line 800:
end if
end if
</p>
</p>
<br>'''Note:'''
While the above code looks inncent enough, it can ultimately be quite troublesome. To understand why, consider the following scenario:
Before <var class="product>Sirius Mods</var> Version 6.9, the "is instance of"
test, above, was ''not'' provided by <var class="product">Janus SOAP ULI</var>, nor was any
other method that allowed one to determine programmatically the "true"
class of an object.
This ''intentional'' omission was to prevent just the sort of
innocent looking, but ultimately, very troublesome code illustrated
in the example.
To understand why the above example code is troublesome,
consider the following scenario:
<ol>
<ol>
<li>The code works as needed and some special processing is
<li>The code works as needed and some special processing is
done for Otters.
done for <code>Otter</code> objects.
<li>There is other Otter-specific processing.
<li>There is other <code>Otter</code>-specific processing. That processing could be handled by <var>If</var> statements placed wherever needed.
<li>Someone wants to extend the <code>Mammal</code> class with, say, a Ferret class.
That processing could be handled by <var>If</var> statements placed wherever needed.
<li>Someone wants to extend the Mammal class with, say, a
Ferret class.
   
   
The theory of object-oriented programming is that the Ferret class implementer
The theory of object-oriented programming is that the <code>Ferret</code> class implementer
simply has to understand the Mammal class to do what she needs to do.
simply has to understand the <code>Mammal</code> class to do what she needs to do.
But due to all the <var>If</var> tests for the Otter class, adding a new extension
But due to all the <var>If</var> tests for the <code>Otter</code> class, adding a new extension
class to Mammal is quite difficult.
class to <code>Mammal</code> is quite difficult.
Which, if any, of the <var>If</var> clauses that are executed for Otters should be executed
Which, if any, of the <var>If</var> clauses that are executed for <code>Otters</code> should be executed
for Ferrets?
for <code>Ferrets</code>?
Probably, some of them should be (after all, ferrets are very similar
Probably, some of them should be (after all, ferrets are very similar to otters; both are mustelids, that is, members of the weasel family).
to otters; both
And probably some of them should not be executed (most otters are aquatic, ferrets are not).
are mustelids, that is, members of the weasel family).
And probably some of them should not be executed (most
otters are aquatic, ferrets are not).
</ol>
</ol>
   
   
To add a new extension class to the Mammal class, the coder of the
To add a new extension class to the <code>Mammal</code> class, the coder of the
Ferret class has to worry about all the <var>If</var> tests scattered throughout
<code>Ferret</code> class has to worry about all the <var>If</var> tests scattered throughout
code that has nothing to do with the Mammal class.
code that has nothing to do with the <code>Mammal</code> class.
In fact, a successful implementation of the Ferret class might require
In fact, a successful implementation of the <code>Ferret</code> class might require
modifying the code that has the narrowing assignments, adding extra <var>ElseIf</var>
modifying the code that has the narrowing assignments, adding extra <var>ElseIf</var>
clauses with Ferret-specific processing.
clauses with <code>Ferret</code>-specific processing.
   
   
If the approach in this scenario seems antithetical to the idea of
If the approach in this scenario seems antithetical to the idea of
Line 1,097: Line 828:
If it seems like it would end up defeating many of the benefits
If it seems like it would end up defeating many of the benefits
of object-oriented programming, it would.
of object-oriented programming, it would.
This is why <var class="product">Janus SOAP ULI</var> initially did not support narrowing assignments &mdash;
This is why <var class="product">SOUL</var> requires special syntax to pewrform narrowing assignments &mdash;
they are a formula for defeating many of the benefits of object-oriented programming.
they are a formula for defeating many of the benefits of object-oriented programming.
   
   
Line 1,106: Line 837:
based on an object's "true" class can cause maintainability problems
based on an object's "true" class can cause maintainability problems
whether or not there are narrowing assignments inside the <var>If</var> clauses.
whether or not there are narrowing assignments inside the <var>If</var> clauses.
This is why <var class="product">Janus SOAP ULI</var> initially did not provide any methods for determining an
This is why it is recommended that SOUL programmers not use the Instance Of clause to determine an object's "true" class: this would almost certainly be used to defeat the encapsulation of an object's behavior in the class definition.
object's "true" class: they would almost certainly be used to defeat
the encapsulation of an object's behavior in the class definition.
   
   
If a class, such as the Mammal class, requires extension-class specific
If a class, such as the <code>Mammal</code> class, requires extension-class specific
processing, a far superior approach to If tests based on the object's
processing, a far superior approach to If tests based on the object's
class is to use an approach called '''dynamic dispatch'''.
class is to use an approach called '''dynamic dispatch'''.
This approach is sometimes
This approach is sometimes
called '''dynamic dispatch according to object type''',
called '''dynamic dispatch according to object type''',
and it is the topic of the [[Dynamic dispatch|"Dynamic dispatch"]] section.
and it is the topic of the [[Dynamic dispatch]] section.
   
   
All this said, narrowing assignments can sometimes be useful, possibly
All this said, narrowing assignments can sometimes be useful, possibly
Line 1,127: Line 856:
set of other classes.
set of other classes.
In any case, should the need for narrowing assignments or class tests
In any case, should the need for narrowing assignments or class tests
present themselves in an application, <var class="product>Sirius Mods</var> Version 6.9 and later
present themselves in an application, <var class="product">SOUL</var> provides support for such assignments.
provide support for such assignments.
For more information, see [[Narrowing assignments and class tests]].
For more information see [[Narrowing assignments and class tests|"Narrowing assignments and class tests"]].


==See also==
==See also==
Line 1,136: Line 864:
<li>[[Narrowing assignments and class tests]]
<li>[[Narrowing assignments and class tests]]
<li>[[Enhancement methods]]
<li>[[Enhancement methods]]
<li>[[Janus SOAP essentials]]
<li>[[Object oriented programming in SOUL]]
<li>[[Classes and Objects]]
<li>[[Classes and Objects]]
</ul>
</ul>
 
[[Category:Overviews]]
[[Category:Overviews]]
[[Category:Janus SOAP ULI topics]]
[[Category:SOUL object-oriented programming topics]]

Latest revision as of 16:33, 23 October 2013

Background

Inheritance and polymorphism are two closely related but subtly different concepts that are often considered cornerstones of object-oriented programming. Inheritance allows a programmer to create an object class that has all the characteristics of another object class (or that inherits all that classes attributes) and that has additional characteristics or attributes. Such a class is often called an extension class, because it extends the capabilities of what is often called a base class.

Polymorphism allows objects of different classes to be used interchangeably. Since the classes that can be used interchangeably are almost always a base class and one of its extension classes, or are extension classes of the same base class, polymorphism is strongly related to inheritance.

To create an extension class of a base class, simply include the Extends phrase in the first Class statement for the class:

class ropeList extends stringList ... end class

As this example illustrates, it is possible to extend many system classes as well as user-defined classes. There are exceptions, however:

  • It is not possible to extend the following types of system classes:
  • It is possible to extend a file class in a specific context. For example, the following Class statement extends the Recordset class for file FOOBAR:

    class fooRecords extends recordset in file foobar

  • It is not possible to generically extend any file classes. For example, the following is not allowed:

    class myRecordset extends recordset

When one class extends another class, members of the base class can be accessed via variables of the extension class. For example, if RopeList extends the system Stringlist class, and %rope is of the RopeList class, any Stringlist class member can be accessed by preceding the member name with the Stringlist class name in parentheses:

%rope:(stringList)add('Added item')

If the creator of the RopeList class wants users of the class to be able to use Stringlist class members without specifying the class name on each invocation, the Inherit keyword can be specified after the class name in the Extends clause on the Class statement:

class ropeList extends stringList inherit ... end class

If Inherit is specified on the Class statement, the Add method in the previous example can be accessed without specifying the Stringlist class:

%rope:add('Added item')

Since the main reason to use extension classes is to add functionality to the base class, you may define Public, Private, Public Shared, and Private Shared members in an extension class, and you may access these members like any other member of any other class:

class ropeList extends stringList inherit public property first is longstring property last is longstring subroutine print variable name is string len 32 end public ... end class

A member of an extension class may have the same name as a member of a base class. In the example above, the RopeList class has a Print subroutine that has the same name as a Stringlist class method. In such a case, an unqualified reference to the member such as the following refers to the member in the extension class:

%rope:print

This is called member hiding, because a member in the base class is hidden by a member in the extension class. In such cases, however, it is always possible to access the base class member by indicating the class name in parentheses, before the member name:

%rope:(Stringlist)print

This accessibility means someone writing an extension class should never assume that a base class member with the same name as an extension class member will never be accessed.

Another way to access a base class member instead of using an extension class member with the same name is to use polymorphism. With polymorphism, one can always assign an extension class variable to a base class variable:

%rope is object ropeList %string is object stringList ... %rope = new %rope:name = 'Percy' %string = %rope %string:print

This assignment is possible because an object of an extension class is also considered to be an object of the base class. Many object-oriented programming textbooks use animals to illustrate this point: if an object is of class Otter, it is also of class Mammal, so there is nothing wrong with assigning it to a Mammal object variable. Once assigned to a base class variable, all member name references access the base class member of that name, even if the extension class has an eponymous member name. If this were not so, someone referencing a member via a base class variable might access a member of a class they did not even know about. Although there are class members that can be overridden by extension classes, these members must be declared as such so that users of the class are aware this might happen. These types of members are discussed in "Dynamic dispatch".

In addition to allowing unqualified access to base class members (via the Inherit keyword in the Extends clause of the Class statement), it is possible to selectively allow unqualified access to individual base class members. In the following class, any reference to the Print member via a RopeList class variable accesses the Stringlist member of that name:

class ropeList extends stringList public property first is longstring property last is longstring inherit print from stringList variable name is string len 32 end public ... end class

The Inherit declaration can also be used to map a base class method to a different extension class name:

class ropeList extends stringList inherit public property first is longstring property last is longstring subroutine print inherit display from stringList print variable name is string len 32 end public ... end class

In this example, a reference to the Display member of the RopeList class actually accesses the Print method of the Stringlist base class. This can be useful if, as in this example, there is a conflict between a base class member name and an extension class member name.

While it is best to use unique member names between extension and base classes, sometimes naming conflicts can happen by accident when a new base class member is added that has the same name as an existing extension class member. The programmer adding the member might not be aware that the member name is already in use in an extension class.

Although adding a member with a name already in use produces a naming issue for applications wanting to use the new base class member name, it does not affect existing applications:

  • References to the extension class member name via extension class variables continue to reference the extension class member (member hiding).
  • Existing applications do not have references to the member name via base class variables or explicit base class specification, because the member name did not exist in the base class.

Multiple inheritance

SOUL allows a class to extend more than a single class. This seems reasonable, as many objects in the real world can be thought of as extensions to more than one base class. For example, one might think of a dog as an extension to a Mammals class and to a Pets class. Or a person's car can be thought of as an extension to an Automobile class and to a Property class.

To extend multiple classes with SOUL, you separate the extended classes with the And keyword in the Class statement Extends clause:

class pet extends animal and property

The Inherit keyword can follow any or all of the extended class names:

class pet extends animal inherit and property

or

class pet extends animal inherit and property inherit

In general, having Inherit keywords on more than one extended class is not recommended, unless those classes are maintained to avoid name collisions. A name collision is generally treated as a compilation error. In the above example, if the Animal and Property classes both had members called Weight, the class declaration would result in a compilation error.

You can prevent name collision errors by specifying the IgnoreDuplicates keyword after the Inherit keyword for a base class for which duplicates are to be ignored:

class pet extends animal inherit and property inherit ignoreDuplicates

Duplicate member names are ignored only between classes that are specified before the base class with the IgnoreDuplicates keyword. The IgnoreDuplicates keyword in the following declaration accomplishes nothing, because there are no base classes before the Animal class:

class pet extends animal inherit ignoreDuplicates and property inherit

A naming collision between the Animal class and the Property class would still cause a compilation error.

When IgnoreDuplicates is specified for an Inherit base class, any member names in that class that match an earlier Inherit base class member name will not be accessible without qualification, if they are being accessed via extension class variables. For example, a class has the following declaration:

class pet extends animal inherit and property inherit ignoreDuplicates

If both the Animal and Property classes have a member called Weight, the following call references the Weight member of the Animal class (assuming the Pet class has no Weight member that hides Animal's Weight member):

%tinky is object pet ... print %tinky:weight

In this case, the Property class's Weight member is accessible by qualifying it with the class name:

%tinky is object pet ... print %tinky:(property)weight

Using IgnoreDuplicates can be risky if the base classes in question are not maintained to avoid name collisions, because the addition of a member to the earlier base class can break existing code. For example, if the Property class above has a function with no parameters called Value, and the Animal class has no eponymous member, the following statements readily compile:

%tinky is object pet ... print %tinky:value

However, if someone adds a subroutine called Value to the Animal class, the Print statement above would fail to compile. Since the IgnoreDuplicates clause in the Class statement dictates that an Animal class Value pre-empts a Property class one, the compilation failure would be because the value of a subroutine cannot be printed.

Using multiple Inherits and IgnoreDuplicates without care can be even more hazardous: if a function, property, or variable called Value were added to the Animal class in the preceding example, the code above would compile but start to work differently, possibly producing subtle and difficult to diagnose problems.

Repeat inheritance

With multiple inheritance, it is possible to define an extension class where the same base class appears more than once in the inheritance tree. For example, if the class MaleDriver extends the Driver class and the class AmericanDriver extends the Driver class, it might seem logical to create a MaleAmericanDriver class that extends both the MaleDriver and the AmericanDriver class. If this were done, however, the MaleAmericanDriver would end up with the Driver class as a base class twice — once via the MaleDriver class, and once via the AmericanDriver class. This is an example of repeat inheritance and was not allowed by SOUL until support was introduced in Sirius Mods version 7.8.

While the past lack of support for repeat inheritance might have seemed a significant problem, many cases where repeat inheritance seems necessary are actually cases where the class model is simply wrong. In the above example, it would probably have made more sense for there to be a Male class and an American class. In that case, the MaleDriver class would have extended the Male and Driver classes, the AmericanDriver class could have extended the American and Driver classes, and the MaleAmericanDriver class could have extended the Male, American, and Driver classes, eliminating the need for repeat inheritance.

The repeat-inheritance-free model, above, has many other advantages. First, it makes it much easier to define other classes, such as a MaleAmerican class, or a MexicanDriver class. Second, it reduces inheritance tree depth by one level, which makes coding and debugging easier. Finally, it just makes more sense — maleness is orthogonal to whether or not someone drives, so why should there be a MaleDriver class but no Male class?

Still, there might be some special cases where repeat inheritance does make sense so SOUL supports repeat inheritance, with some restrictions. The chief restriction on repeat inheritance is that if a class is repeatedly inherited by another class, the inherited class must only be inherited multiple times under a class that extends the class directly.

The most common case is likely to be a repeatedly inherited class extended directly by the outermost extension class. For example, if in the above example, AmericanMale extended the Male class and MaleDriver also extended the Male class, and the AmericanMaleDriver class extended MaleDriver and AmericanMale, then it would also have to directly extend the Male class. In other words, the Class statement for the AmericanMaleDriver class would have to look something like:

class americanMaleDriver extends americanMale inherit - and maleDriver inherit - and male inherit

If a class AmericanMaleTruckDriver extended AmericanMaleDriver (and no other classes) that would be allowed — while Male is repeatedly inherited, all repeat inheritances are under the AmericanMaleDriver class which extends the Male class directly.

It would also be valid if AmericanMaleTruckDriver extended AmericanMaleDriver and Worker, assuming Worker did not also extend the Male class. If, in fact, AmericanMaleTruckDriver extended AmericanMaleDriver and MaleWorker, and MaleWorker extended the Male class (as would be expected), then AmericanMaleTruckDriver would also have to extend the Male class directly:

class americanMaleTruckDriver extends americanMaleDriver inherit - and maleWorker inherit - and male inherit

Of course, in real world applications this level of inheritance would be exceedingly rare and would suggest an overenthusiasm for inheritance that might ultimately lead to trouble,

Regardless, if repeat inheritance does rear its head, the programmer should be aware of the rules for variable and method references in the repeatedly inherited class.

First, any instance (non-shared) variables in a repeatedly inherited class will have multiple copies in the extending class — one for each repeated inheritance, regardless of the depth. For example, suppose BirthDate is a variable in the Male class (this is silly, of course, because females would also have BirthDates). Then the AmericanMaleTruckDriver class would have four BirthDate variables: one under the AmericanMale class, one under the MaleDriver class, one under the AmericanMaleDriver class, and one under the AmericanMaleTruckDriver class. The one that is actually referenced depends on the class of the object variable used to reference the variable. For example, given the following:

%maleDriver is object maleDriver %americanDriver is object americanDriver %americanMaleDriver is object americanMaleDriver %americanMaleTruckDriver is object americanMaleTruckDriver

Then %maleDriver:birthDate would reference the BirthDate directly under the MaleDriver class, while %americanMaleDriver:birthDate would reference the BirthDate directly under the AmericanMaleDriver class, and so on. So, if we did:

%americanMaleDriver = %americanMaleTruckDriver %americanMale = %americanMaleDriver %americanMaleTruckDriver:birthDate = '19721106' %americanMaleDriver:birthDate = '19930529' %americanMale:birthDate = '19580212' printText {~} = {%americanMaleTruckDriver:birthDate} printText {~} = {%americanMaleDriver:birthDate} printText {~} = {%americanMale:birthDate}

The following would be printed:

%americanMaleTruckDriver:birthDate = 19721106 %americanMaleDriver:birthDate = 19930529 %americanMale:birthDate = 19580212

Of course, this doesn't make a heck of a lot of sense and the oddity of it all should make one uneasy. It suggests that one should be very cautious about the use of variables in classes that are repeatedly inherited. In any case, the variable referenced in a repeatedly inherited class is the copy in the inherited class that is most directly extended by the class of the object variable used in the reference.

Similarly, if a non-overridable method is invoked, the question arises as to which repeated Male class's variables are referenced by the %this passed to the method. The answer is again, of course, the copy of the inherited class that is most directly extended by the class of the object variable used in the reference. So, in the above example, if the Male class had this method:

subroutine showBirthDate printText The birth date is: {%this:birthDate} end subroutine

Then if you specify the following:

%americanMaleTruckDriver:showBirthDate %americanMaleDriver:showBirthDate %americanMale:showBirthDate

The result is:

The birth date is: 19721106 The birth date is: 19930529 The birth date is: 19580212

For overridable (or abstract) methods, regardless of the class of the object variable used to reference it, %this refers to the copy of the inherited class that is most directly extended by the class of the object referenced. For example, if the Male class had this method:

function birthYear overridable return %this:birthDate:left(4) end function

And these statements are executed:

printText {~} = {%americanMaleTruckDriver:birthYear} printText {~} = {%americanMaleDriver:birthYear} printText {~} = {%americanMale:birthYear}

The result is the following, because all the object variables reference an AmericanMaleTruckDriver object:

%americanMaleTruckDriver:birthYear = 1972 %americanMaleDriver:birthYear = 1972 %americanMale:birthYear = 1972

If the AmericanMale class had a method that implemented (overrode) the BirthYear method, it would still not be invoked in this case, because it overrides the method in the copy of the Male class that's extended by the AmericanMale class. However, if the AmericanMaleTruckDriver class had this method:

function birthYear implements birthYear in male return 1992 end function

And these statements are executed:

printText {~} = {%americanMaleTruckDriver:birthYear} printText {~} = {%americanMaleDriver:birthYear} printText {~} = {%americanMale:birthYear}

The following would be printed, because the BirthYear method in the AmericanMaleTruckDriver class overrides the BirthYear method in the outermost copy of the Male class:

%americanMaleTruckDriver:birthYear = 1992 %americanMaleDriver:birthYear = 1992 %americanMale:birthYear = 1992

Interfaces

Multiple inheritance provides all the capabilities that interfaces provide with more flexibility. Specifically, an interface is simply a class in which all methods are abstract (and so must be overridden) and which contains no variables. In SOUL, it is easy enough to define an abstract class that has no variables and in which all methods are abstract. This pretty much provides the exact same functionality as interfaces, meaning that there is really no compelling reason to have support for special interface classes in SOUL.

The main reason for the existence of interfaces in other languages is the lack of support for multiple inheritance. In such languages, while there is no generalized multiple inheritance support, there is support for multiple inheritance of interface classes, classes which have no data and in which all methods are abstract. These restrictions avoid most of the semantic and implementation issues that arise with support for true multiple inheritance. And, while one might argue that these restrictions are a good thing, there is nothing in SOUL that prevents one from adhering to these restrictions by choice.

On the other hand, it is also conceivable that some of the restrictions on interfaces could cause problems. For example:

  • The fact that all methods in an interface must be abstract means that if one wants to add a method to an interface, all extenders of that interface must be updated to provide an implementation of that new method. Obviously, this is likely to make interfaces somewhat brittle.

    With SOUL, you can use an abstract class with no data exactly as an interface. But if you want to add a method, you can add a default implementation for that method and make it overridable, not abstract. This means that methods can easily be added to interface-like classes in SOUL without creating a rippling effect that requires updating of all extenders of the class.

  • The absence of non-abstract methods in interfaces seems like a less important restriction than the absence of instance variables in avoiding the semantic and implementation issues brought about by multiple inheritance. As such, it is quite possible that at least this restriction would be dropped some day in languages that support interfaces but not multiple inheritance.

  • The absence of instance-specific variable support for interfaces can also be a problem.

    For example, considered a ScheduledObject class where one can use a generic queue manager to schedule activities on the object. Since the actual activities would be class-specific, it would seem like an inheritance model would make a lot of sense — the class would provide overrides of standard methods in the ScheduledObject class to perform a specific activity. But, it would also seem that the ScheduledObject class would really need instance-specific variables such as queue pointers, status flags, priorities, time stamps and the like.

    Of course, this is generally not a problem. Any class that wants to be a ScheduledObject would simply extend the ScheduledObject class. However, it's not inconceivable that one might have a class that might sometimes be used as a ScheduledObject, and sometimes not.

    For example, one might have a Payment class that is sometimes used for display purposes. That is, it might be loaded from a database file and then sent to a web page. On the other hand, sometimes Payment actions need to be scheduled and, for that purpose, it would be useful to have a class that was both a Payment and a ScheduledObject, say a ScheduledPayment. With multiple inheritance, this is easily accomplished, but with interfaces this is impossible because both the Payment class and the ScheduledObject class have variables so neither can be an interface, so no class can extend both of them. The solution would be to simply make the Payment class extend the ScheduledObject class. While this means that Payment objects always carry around the baggage of the ScheduledRequest class, it's probably an acceptable solution in most cases.

    It becomes problematic, however, if there's yet another class that Payment needs to extend like, say, a BusinessTransaction class. That class would likely have its own variables and so, again, would not lend itself to being implemented as an interface.

All this said, a large amount of inheritance is not common in business applications, and in the vast majority of classes, neither interfaces nor multiple inheritance would be necessary. However, in the odd cases where they are, multiple inheritance provides everything interfaces do and then some. For this reason, and because SOUL supports multiple inheritance, there is no need for SOUL to also provide support for interfaces.

Inheritance and constructors

An extension class might or might not have any constructors. If an extension class has no constructors, the default (New) constructors for the base classes are called in sequence with no parameters. However, if any of the base class constructors require parameters or don't have the default New constructor, a constructor is required for the extension class. Of course, even if a constructor is not required for an extension class, one can be provided as needed.

In any case, if an extension class has a constructor, that constructor is required to complete the construction of the object for all base classes. This is accomplished with the Construct statement, which is followed by an invocation of the shared New method or by invocation of any other constructor. The Construct statement must be followed by the class name (using the shared method invocation syntax), followed by the constructor name, followed by any constructor parameters.

For example, if extension class FooBar, of classes Foo and Bar, had a constructor, that constructor might look like:

constructor new construct %(bar):new construct %(foo):new end constructor

Construct statements are required for both the Foo and Bar class, whether or not they have explicit constructors. If the Bar class New constructor had a parameter, the constructor might look like:

constructor new(%height is float) construct %(bar):new(%height) construct %(foo):new end constructor

It is also possible to invoke constructors other than New. For example, if the Foo class had a NewFoo constructor, the following would be allowed:

constructor new(%height is float) construct %(bar):new(%height) construct %(foo):newFoo end constructor

It is possible to have statements in addition to Construct statements in an an extension class constructor:

constructor new(%height is float) %height = %height + 1 construct %(bar):new(%height) print 'Returned from Bar constructor' construct %(foo):newFoo print 'Returned from Foo constructor' end constructor

And, in fact, the Construct statements can be inside of If clauses:

constructor new(%height is float) if %height lt 10 then construct %(bar):new(10) else construct %(bar):new(%height) end if construct %(foo):newFoo end constructor

Rules for constructors and Inheritance

A perhaps somewhat arbitrary rule for constructors that is enforced at compile time is that a Construct statement must be physically placed inside a constructor for an extension class of the base class being constructed.

In addition to that rule are four major inheritance-specific rules for constructors:

  • It is invalid to refer to a base class member until the constructor for that base class has been entered, that is, until a Construct statement has been issued for that base class.
  • It is invalid to invoke multiple Construct statements against the same base class for the same object instance.
  • When exiting from an extension class constructor, Construct statements must have been issued for all base classes for the object.
  • The %this variable cannot be changed by assignment within a base class constructor. Because an extension class constructor that does a Construct has its own implicit or explicit %this to reference the extension object being constructed, assigning to %this in the base class constructor cannot be allowed to have an effect on the extension class.

    For example, a base class constructor might use %this = null to signal a problem in the base object construction. An alternative to using %this=null in this case is to use %this:discard. Another alternative is to throw an exception. A Throw statement that executes within a constructor causes an automatic Discard of the object being constructed.

All violations of these rules are caught at run-time. This allows more flexibility in writing constructors at the cost of the compile-time checks for rules conformance.

Polymorphism

Polymorphism is the ability of an object variable to refer to objects of the variable's class, or to objects of an extension class of the variable's class. For example, if there is a Mammal class, and the Otter class extends the Mammal class, an object variable of the Mammal class could refer to a Mammal object or an Otter object (since all Otters are Mammals). An Otter object variable can always be assigned to a Mammal variable:

%tarka is object otter %fuzzy is object mammal ... %tarka = new ... %fuzzy = %tarka

After the assignment, the %fuzzy variable can be used as any other Mammal object variable, even though the referenced object is actually an Otter — again, all Otters are Mammals. Note, however, that the %fuzzy variable cannot be used to invoke Otter-specific methods, because the determination of which method is to be run for a method invocation is done at compile-time, and the compiler has no way of knowing that %fuzzy would end up referencing an Otter object.

Polymorphism can also be used in cases of implied assignments. For example, if there is a method in class Zoo that takes a Mammal input object:

class zoo subroutine brushFur(%animal is object mammal) end class

It is possible to invoke this method passing an Otter variable as the input parameter:

%tarka is object otter %bronx is object zoo ... %tarka = new ... %bronx:brushFur(%tarka)

This is logically equivalent to assigning %tarka to the %animal input parameter.

A slight variant on this is when a base class method is called via an extension class variable, either via the Inherit keyword in the Extends clause, an Inherit statement in the extension class Public or Private block, or an explicit base class reference on the invocation.

For example, if the Otter class is defined as follows:

class otter extends mammal inherit

And if there is a LiveBirth method in the Mammal class, the following code calls that method:

%tarka is object otter ... %tarka = new ... %tarka:liveBirth

Part of this call is equivalent to polymorphic assignment of the %tarka variable of class Otter to the %this variable of class Mammal in the LiveBirth method. Exactly the same kind of implied assignment is done if the method invocation is via an explicit class specification:

%tarka is object otter ... %tarka = new ... %tarka:(mammal)liveBirth

All the examples discussed here are explicit or implicit assignments from extension class variables to base class variables. This kind of assignment is sometimes called a widening assignment: an extension class is considered to be a subset of the base class (again, think Otter and Mammal), and assignment from an extension class variable to a base class variable is from a more specific class to a more general class, hence a widening assignment.

Like many other object-oriented languages, SOUL also allows what are called narrowing assignments — assignments from a base class variable to an extension class variable. Because narrowing assignments are usually not recommended, special syntax is required in SOUL to perform them. That is, the following assignment from %fuzzy to %tarka is not allowed and results in a compilation error:

%tarka is object otter %fuzzy is object mammal ... %fuzzy = %(otter):new %tarka = %fuzzy

While initially this might seem like a rather arbitrary and problematic limitation on polymorphism, there are some good reasons why this is not the case:

  • It is exceedingly rare that one would even knowingly try such an assignment, much less need to do such an assignment.
  • Such an assignment might fail.

In the above example, it's clear from the code that %fuzzy would reference an Otter object. But, suppose %fuzzy actually contained a reference to a Kangaroo object, which is also a Mammal but is most definitely neither a base nor an extension class of Otter. It would seem clear that the assignment should fail and that, in the general case, the failure of the assignment could only be determined at run-time.

Many languages that allow narrowing assignments simply compile the assignments, and if the assignment fails, produce a run-time error. Such languages make it the programmer's job to write code in such a way as to avoid such errors. Other languages that allow narrowing assignments, require the assignments to use a special syntax for the narrowing assignments to make it clear that the programmer meant to do what is being done, and to make it clear to anyone reading the code that something "funny" is going on.

Unfortunately, in the case of narrowing assignments, almost anything a programmer is likely to do to prevent narrowing assignment errors is likely to have bad code maintainability implications. To understand why, it is worth continuing the Otter and Mammal example.

Presumably, the programmer does not want request cancellation errors as a matter of course, so would probably do something like the following:

%tarka is object otter %skippy is object kangaroo %fuzzy is object mammal ... if %fuzzy is instance of otter then %tarka = %fuzzy ... do some otter specific processing end if

While the above code looks inncent enough, it can ultimately be quite troublesome. To understand why, consider the following scenario:

  1. The code works as needed and some special processing is done for Otter objects.
  2. There is other Otter-specific processing. That processing could be handled by If statements placed wherever needed.
  3. Someone wants to extend the Mammal class with, say, a Ferret class. The theory of object-oriented programming is that the Ferret class implementer simply has to understand the Mammal class to do what she needs to do. But due to all the If tests for the Otter class, adding a new extension class to Mammal is quite difficult. Which, if any, of the If clauses that are executed for Otters should be executed for Ferrets? Probably, some of them should be (after all, ferrets are very similar to otters; both are mustelids, that is, members of the weasel family). And probably some of them should not be executed (most otters are aquatic, ferrets are not).

To add a new extension class to the Mammal class, the coder of the Ferret class has to worry about all the If tests scattered throughout code that has nothing to do with the Mammal class. In fact, a successful implementation of the Ferret class might require modifying the code that has the narrowing assignments, adding extra ElseIf clauses with Ferret-specific processing.

If the approach in this scenario seems antithetical to the idea of encapsulating object behavior in the class definition, it is. If it seems like it would end up defeating many of the benefits of object-oriented programming, it would. This is why SOUL requires special syntax to pewrform narrowing assignments — they are a formula for defeating many of the benefits of object-oriented programming.

An astute reader will note that it is not so much the narrowing assignments that cause the long-term maintainability problems, as it is the If statements that make narrowing assignments possible to do safely. In fact, If clauses based on an object's "true" class can cause maintainability problems whether or not there are narrowing assignments inside the If clauses. This is why it is recommended that SOUL programmers not use the Instance Of clause to determine an object's "true" class: this would almost certainly be used to defeat the encapsulation of an object's behavior in the class definition.

If a class, such as the Mammal class, requires extension-class specific processing, a far superior approach to If tests based on the object's class is to use an approach called dynamic dispatch. This approach is sometimes called dynamic dispatch according to object type, and it is the topic of the Dynamic dispatch section.

All this said, narrowing assignments can sometimes be useful, possibly in infrastructure classes that are extended by many classes, though, arguably this is just the sort of case where one should go to the trouble of fully formalizing class behavior via dynamic dispatch, rather than using the questionable techniques of class tests and narrowing assignments. Perhaps better arguments for narrowing assignments would be "quick and dirty" applications where formalizing all class behavior would be overkill, or base classes that are not intended to be extended by any but a specific set of other classes. In any case, should the need for narrowing assignments or class tests present themselves in an application, SOUL provides support for such assignments. For more information, see Narrowing assignments and class tests.

See also