Contents Previous Next Index
|
9 Object Oriented Programming
|
|
|
9.1 In This Chapter
|
|
•
|
A brief introduction to Object Oriented Programming will be presented.
|
•
|
A description of how Object Oriented Programming is implemented in Maple.
|
•
|
How to override operators and engine routines using Objects.
|
|
|
9.2 Introduction To Object Oriented Programming
|
|
Objects are a programming tool that allows data and procedures to be encapsulated together. For example, an object could be created to represent a car. A car object might have variables to track its position, velocity and steering position. The car object might also have procedures to accelerate the car and to adjust the steering. A further procedure could be implemented to update the car's position and velocity based on the current acceleration, velocity and steering. Multiple car objects could be used to represent multiple cars, each with their own positions and velocities, but sharing the same procedures for how the cars move.
Objects can also restrict access to certain variables and procedures. For example the car object would allow other code to call a routine to adjust the steering, but may not allow external code to set the value of the steering variable directly. Although this may seem restrictive, it allows the object to control its internal state. In the car example, it could limit the range of steering.
|
Terminology
|
|
The variables in an object that store the data and procedures are referred to as the object's members. Procedures associated with an object are called methods. Object members have access controls which limit where the members can be accessed from, similar to modules. Members declared exported can be accessed from anywhere. Members declared local can only be accessed from within the object's methods. Objects are instances of a class. A class describes the exports and locals that each instance of the class (the objects) will have.
|
|
Benefits of Object Oriented Programming
|
|
Benefits of object oriented programming are:
–
|
The implementation of a class can be changed radically without changing the interface of exported methods. Thus code that uses the objects will not need to change when the internal implementation changes.
|
–
|
As objects are self contained, they can be reused.
|
–
|
Objects can define methods that allow them to integrate with existing Maple routines. Thus users can create objects that can be used like built-in types.
|
–
|
A set of classes can implement a common set of exports. Thus a procedure that uses only the common exports will work with any objects from any of the classes without needing to know which classes the objects belong to.
|
|
Good object oriented design can be difficult. In particular, identifying which concepts should be represented as objects can be tricky. A good rule of thumb is that objects should be your "nouns" and methods should be "verbs". Thus you would create an object to represent a car and you call a method to accelerate the car.
|
|
9.3 Objects in Maple
|
|
|
Creating a New Class of Objects
|
|
To create a new class of objects, use the named module declaration syntax with option object.
>
|
module NewObject()
option object;
...
end module;
|
This will create a new object and assign the new object to the module name (NewObject in the example above). An object created this way will be referred to as a prototype object. In Maple, any object (prototype or other) can be used as a representative of the class.
When declaring an object the members must be declared as either local to the object, using a local declaration or exported, using an export declaration. A member that is declared local can only be accessed from the object's methods, or other object's methods of the same class. A member that is exported, can be accessed anywhere.
By default, the values assigned to the object's members are unique to the object. That is, two objects of the same class can have different values assigned to their members. However some members, member procedures in particular, are shared among all objects of a class. Thus members can also be declared as static. A static member stores only one value that is common to all objects of a class.
|
|
Creating More Objects
|
|
Once a prototype object exists, it can be used to create new objects using the Object routine. The Object routine creates a new object of the same class as the object passed into Object.
>
|
newObj := Object( existingObject );
|
By default, the newly created object will have its members assigned the same values as the object passed to Object. However by implementing a ModuleCopy routine, the object can perform different actions when new instances are created. A ModuleCopy routine can accept additional arguments that are passed into the Object routine.
>
|
newObj := Object( existingObject, arg1, arg2, ... );
|
|
|
Objects and Types
|
|
All objects are of type object. In addition type and :: can be used to determine if an object is a instance of a particular class by passing an object of the expected class as the type. You can refine this type checking by defining the ModuleType method.
|
|
|
9.4 Methods
|
|
Methods are procedures assigned to the members of an object. Methods have a few differences from normal procedures.
|
Methods Can Access Object Locals
|
|
A method belonging to a particular class can access both the local and exported members of any object of that class. This allows methods to access and manipulate the internal states of their objects without requiring the objects to export accessor procedures.
|
|
Method Names Should Be Declared static
|
|
In Maple, most method names should be declared as static. In most cases, all objects of the same class use the same procedures for their methods. If the method name is not declared static, each object will have a separate copy of the procedure. This can be quite wasteful.
There are some instances where an object will have a non-static method. However unless you intend different objects to have different procedures assigned to the method, your method should be static.
|
|
Methods Are Passed the Objects They Manipulate
|
|
Some object oriented languages associate method calls with a particular object. That object is represented via a self variable or by allowing direct access to that object's members. Maple does not give a particular object special significance in that way. Instead, all objects that a method needs to manipulate must be passed as parameters.
|
|
Calling Methods
|
|
To call an object's method, call the method as a standard function call and pass the object in as an argument.
>
|
method( ..., object, ... );
|
When a function call is evaluated and an object is passed in as an argument, the object is searched for an exported procedure with a matching name. If one is found, that member procedure is called with the given arguments.
This search proceeds from left to right, so the first object with a matching method is used as the class whose method is invoked.
|
|
Objects in Indexed Function Calls
|
|
When making an indexed function call (of the form func[index](args)) Maple will also check the indices (index) for a matching object as well as the arguments. If a matching object is found in the indices, that object will be used before one found in the arguments.
Search an index sequence is also performed from left to right.
|
|
Special Methods
|
|
There are a set of special methods that a class can define that will automatically be used in various situations. Not all of these methods make sense for all objects. See the method specific help pages for complete details.
ModuleCopy: The ModuleCopy method is invoked when a object is copied via the Object routine.
ModuleType: The ModuleType method is invoked when an object is passed into the type routine. It allows a module to have a more precise type check of objects of a particular class.
ModulePrint: The ModulePrint method is invoked when an object is pretty-printed.
ModuleDeconstruct: The ModuleDeconstruct method is invoked when an object is converted to a 1 dimensional form, usually Maple syntax.
ModuleApply: The ModuleApply method is invoked when an object is used as a function in a function call expression.
ModuleLoad: The ModuleLoad method is invoked when the object class is read in from a library.
ModuleUnload: The ModuleUnload method is invoked when an object is collected.
ModuleIterator: The ModuleIterator method creates an interface that can be used to iterate over the contents of the object.
|
|
|
9.5 Overloading Operators
|
|
Objects can define methods which allow them to control what happens when those objects are used with various operators. For example, if an object implements a + method, then that method will be invoked if the object appears in a sum expression.
By overloading operators, objects can be used in Maple expressions. This, combined with overloading built-in routines, allows objects to be used naturally in general Maple expressions.
|
Supported Operators
|
|
The following operators can be overloaded by an object:
+
|
-
|
*
|
/
|
^
|
!
|
.
|
=
|
<>
|
<
|
<=
|
>
|
>=
|
|
|
and
|
or
|
not
|
xor
|
implies
|
intersect
|
union
|
minus
|
subset
|
in
|
|
|
The following operators, in particular, cannot be overridden:
Note: These lists are not the same as the operators that can and cannot be overridden using a use statement.
|
|
Implementing Operators
|
|
In general implementing operators is similar to implementing normal methods. However particular operators have rules that must be followed if they are to be implemented correctly.
The rules for the various operators are documented on the Object,operators help page.
|
|
|
9.6 Overloading Built-in Routines
|
|
Objects can implement methods to override some built-in routines (like convert or abs). These methods will be invoked when objects are passed as arguments to the corresponding built-in routines. By overriding built-in routines, user-defined objects can be used in normal Maple expressions. This, combined with overloading operators, allows objects to be used naturally in general Maple expressions.
Any routine implemented in Maple code can be overloaded. However, not all built-in routines (routines implemented in the Maple kernel) can be overloaded.
|
Overridable Built-in Routines
|
|
The following built-in routines can be overloaded by object methods:
Some overloadable built-in routines have a specific interface that must be followed. The interfaces for the overloadable built-ins can be found on the object,builtins help page.
|
|
|
9.7 Examples
|
|
The following example shows a class for objects representing integers modulo a given base.
(* create a new class of objects with a prototype object
>
|
i0m5 := IntMod( 0, 5 );
|
| (1) |
>
|
i1m5 := Object( i0m5, 1 );
|
| (2) |
>
|
type( i1m5, 'IntMod' );
|
| (3) |
>
|
type( i1m5, 'IntMod'(3) );
|
| (4) |
>
|
type( i1m5, 'IntMod'(5) );
|
| (5) |
| (6) |
| (7) |
| (8) |
| (9) |
| (10) |
| (11) |
>
|
convert( i3m5, 'integer' );
|
| (12) |
Error, (in IntMod:-convert) cannot convert into IntMod from 3
| |
>
|
i2m5 * i4m5 * y * f(x);
|
| (13) |
| (14) |
| (15) |
| (16) |
| (17) |
| (18) |
| (19) |
| (20) |
| (21) |
| (22) |
| (23) |
|
|
9.8 Avoiding Common Mistakes
|
|
|
Overloaded Operators and Built-in Routines Must Handle All Possibilities
|
|
A object's method will be invoked whenever that object appears in a matching function call, regardless of the object's position in the argument sequence. Thus when implementing operators and overloading built-in routines, it is important the handle all the cases where the object could appear.
In the following example it might be easy to assume that when member is called the object will be the first argument (the container). However, it is also possible that the object will appear as the second argument (the element being searched for).
>
|
module Container()
option object;
local t := table();
export insert::static := proc( c::Container, a, $ )
c:-t[a] := 1;
NULL;
end;
export member::static := proc( c, e, $ )
if ( c::Container ) then
if ( c:-t[e] = 1 ) then
return e;
else
return 0;
end;
else
return ':-member'( c, e );
end;
end;
end:
|
>
|
container := Object( Container ):
|
>
|
insert( container, a ):
|
>
|
member( container, a );
|
| (24) |
>
|
member( [container], container );
|
| (25) |
For some possibilities, the correct approach is to simply pass the arguments on to the Maple routine. When doing so, care must be taken to access the correct version of the routine.
|
|
Make Sure to Access the Correct Routine
|
|
When overloading operators and built-in routines, those overloads will be used within the implementation of the object itself. This means that care should be taken to call the global version of a routine when it is required. In the member overload shown earlier, the code invokes the global version of member, by using quotes and :-. Failing to do so can lead to infinite recursions and other unexpected behavior.
|
|
Be Aware of NULL
|
|
Be careful when assuming that operators and built-in routines will always be passed a certain number of arguments. Many will accept NULL as an argument, and this may lead to fewer arguments then expected.
>
|
module Wrapper()
option object;
local value := 10;
export `=`::static := proc( l, r, $ )
( l::Wrapper and r::Wrapper and l:-value = r:-value );
end;
end:
|
>
|
cp := Object( Wrapper ):
|
| (26) |
| (27) |
| (28) |
|
|
Lexical Scoping Does Not Circumvent local
|
|
Members that are declared as local can only be accessed from within the class's methods. This means that methods cannot use lexical scoping to pass values to nested procedures.
>
|
module LexicalObj()
option object;
local a;
export b :: static := proc(mm :: m, f, lst :: list, $)
print(mm:-a);
return map(x -> f(mm:-a, x), lst);
end;
end:
|
| (29) |
In this example, we can print the value of a in b because b is a method. However the map fails because the arrow procedure is not a member and thus does not have access to a.
|
|
|
Contents Previous Next Index
|