Inheritance and polymorphism: Difference between revisions
m (→Interfaces) |
mNo edit summary |
||
(16 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
==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 | 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> | ||
<li>It is possible to extend a file class in a specific context. | |||
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 | |||
<var class="product"> | ==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"> | 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 | allowed by <var class="product">SOUL</var> until support was introduced in <var class="product">Sirius Mods</var> version 7.8. | ||
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 | 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"> | 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"> | 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"> | 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 | 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"> | <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 | 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"> | 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 | |||
<li>The absence of non-abstract methods in interfaces seems like a less | |||
important restriction than the absence of instance variables in | 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 | ||
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 | <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, | 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"> | For this reason, and because <var class="product">SOUL</var> supports multiple inheritance, | ||
there is no need for <var class="product"> | 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>[[ | 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=== | |||
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> | ||
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. | |||
This | |||
for | |||
==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. | ||
Like many other object-oriented languages, <var class="product">SOUL</var> also allows what are called '''narrowing assignments''' | |||
— assignments from a base class variable to an extension class | — 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. | ||
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. | ||
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. | |||
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> | ||
While the above code looks inncent enough, it can ultimately be quite troublesome. To understand why, consider the following scenario: | |||
To understand why | |||
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 | 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"> | This is why <var class="product">SOUL</var> requires special syntax to pewrform narrowing assignments — | ||
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 | 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: | |||
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 | 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"> | present themselves in an application, <var class="product">SOUL</var> provides support for such assignments. | ||
For more information, see [[Narrowing assignments and class tests]]. | |||
For more information see [[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>[[ | <li>[[Object oriented programming in SOUL]] | ||
<li>[[Classes and Objects]] | <li>[[Classes and Objects]] | ||
</ul> | </ul> | ||
[[Category:Overviews]] | [[Category:Overviews]] | ||
[[Category: | [[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:
- Collections
- Enumerations
- The XmlNode class
- It is possible to extend a file class in a specific context.
For example, the following
Class
statement extends the Recordset class for fileFOOBAR
: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:
- The code works as needed and some special processing is
done for
Otter
objects. - There is other
Otter
-specific processing. That processing could be handled by If statements placed wherever needed. - Someone wants to extend the
Mammal
class with, say, a Ferret class. The theory of object-oriented programming is that theFerret
class implementer simply has to understand theMammal
class to do what she needs to do. But due to all the If tests for theOtter
class, adding a new extension class toMammal
is quite difficult. Which, if any, of the If clauses that are executed forOtters
should be executed forFerrets
? 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.