|
Developed
at the Polish-Japanese Institute of Information
Technology © Copyright by ODRA team, © Copyright by PJIIT |
|
|
|
ODRA – Object
Database for Rapid Application development Description and Programmer Manual |
||
|
by Radosław Adamus and
the ODRA team |
||
9. SBQL Classes, Methods and Bidirectional PointersThe ODRA object model deals with
classes and static inheritance[1].
Classes are pieces of a source code, as usually in object oriented programming
languages. After compilation classes in ODRA are database objects similar to
regular objects described before. Compiled classes are stored in an object
store on usual principles. Operationally, however, classes behave differently
than objects. The difference concerns the assumption that classes are created
in a special manner and are used to introduce and store invariants of objects
- class instances. In the current implementation classes are not directly
accessible through a query language. The use of classes is indirect, through
the use of objects being class instances. ODRA classes follow static
inheritance known from UML and many programming languages such as C++ and
Java. The ODRA object model allows for multiple inheritance, i.e. a class can
inherit from more than one classes. No special means for resolving name
conflicts concerning properties inherited from different classes is provided
(just like e.g. in UML and CORBA). We have concluded that name conflicts can
be easily avoided by some naming convention, hence the problem is not
sufficiently significant to be treated by special syntactic, semantic and
pragmatic options. In the further ODRA releases we plan to implement dynamic
object roles and dynamic inheritance that are perhaps the only radical
solution for all the problems with inheritance. 9.1 Class Invariants
Invariants introduced by the class
belong to the following kinds:
Below we describe them in detail. Type. A
class instance type is described by structural (complex) declaration, with
optional set of fields declaration in curly brackets, preceded with the
keyword instance and the optional
instances_name: instance
[instance_name] : { [field_declaration_list]
} Methods. A
method is an ODRA procedure stored inside a class. The current implementation
of ODRA introduces only instance methods (called in the context of class instance).
Class methods are not supported. Inside a method body the current
instance against which the metod is accessible with the keyword self. getName():integer { return
self.name; } Instance name. The class can optionally introduce an invariant name for its
instances. Such a name is optional. However, it the name is not present, a
class instance cannot be the subject of the substitutability principle (decribed later in this chapter). 9.2 Syntax of Class Declaration
class name [extends
superclass_names_list] { instance [instance_name] : { [field_declaration_list] } [metod_declaration_list] } Sample class declarations
class
PersonClass { instance Person : { f_name : string; s_name : string; age :
integer; address : record { city : string; street : string; number : integer; } } getFullName():string{ return
f_name + “ “ + l_name; } } class
EmployeeClass extends PersonClass
{ instance Employee : {
salary : integer; works_in : ref Department; prev_job : record { comp_name : string; from : integer; till : integer; } } changeSalary(amount
: integer){ salary := amount; } moveEmployee(newDept
: ref Department){ //... } } class
StudentClass extends PersonClass {
instance
Student : { scholarship:integer; } changeScholarship(amount:integer){
scholarship : = amount; } setSholarship(newScholar:integer) { scholarship := newScholar; } } class
EmpStudentClass extends EmpClass,
StudentClass { instance
EmpStudent : { } getTotalIncomes():integer { return
self.(salary + scholarship); } } 9.3 Declaration of Class Instances
Although a class may determine an instance
name, by itself it does not declare the default collection (an extent) of
class instances. Declaration of of class instances is to be done separately.
The syntax for a declaration is the same as for ordinary variables. The only
difference is that a class name is to be used instead of type. For example,
declarations presented below introduce four variables that identify
collections of class instances: Person:
PersonClass[0..*]; Emp: EmpClass[1..*]; Student:
StudentClass[0..*]; EmpStudent: EmpStudentClass[0..*]; 9.4 Substitutability
If a given class defines an
instance name then instances of the class can be a subject of the
substitutability principle. It allows for passing instances of a sub-class
everywhere a super-class instance is expected. Because SBQL addresses data
structures rather that data models, the substitutability is available on the
level of object collections and their names rather than class names. For
sample classes and data introduced in the previous sections it means that
instances of the most specific class named EmpStudent are also available
through the instance names defined in its super-classes (Student, Emp and
Person). Thus the simple query Person; returns all the persons, also
those that are instances of the PersonClass sub-classes. We didn’t find
a consistent definition of the substitutability when names of class instances
are not determined by their classes. The main problem with this case concerns
typing and binding rules in connection with collections and the open-close
principle. 9.5 SBQL Operators Specific to the Object
Model with Classes
9.5.1 Operator instanceof
query ::= query1 instanceof class_name The query1 returns a single reference to a class instance. The instanceof operator tests if the referenced
object is an direct or indirect instance of the class identified with name class_name. Returns boolean TRUE if
the test succeeds, FALSE otherwise. The type checker rises an error if it
discovers that the reference cannot be an instance of any class or if the class_name is not the name of any
class. Examples (Emp where
f_name = “Poe”) instanceof
EmpClass // returns TRUE (Emp where
f_name = “Poe”) instanceof
PersonClass // returns TRUE (Emp where
f_name = “Poe”) instanceof
StudentClass //returns FALSE (Emp where
f_name = “Poe”) instanceof
EmpStudentClass // the result depends // on the database state, because EmpStudentClass
instances // are also
accessible through name Emp. 9.5.2 Operator cast to a class
query ::= (class_name)query1 query1 returns
a bag of references to class instances. The result of the class cast is a sub
bag of references that contains only those instances references from the
source bag that belongs to a class we cast to (identified with name class_name). If none such reference
exists, the result is the empty bag. We can distinguish three types of
class cast:
Examples (StudentClass)
(Person where age > 23); The result of this downcast is a
bag of references to person objects that value of age sub-objects is greater
that 23 and are instances of StudentClass (i.e. that are students older that
23). This kind of cast is equivalent to the selection: Student where age > 23; The result of the query presented
below (including down- and up-cast) is a bag of references to objects having
the value of age greater that 23 and are instances of both StudentClass and EmpClass.
First, the query selects all persons that are older that 23, then the persons
that are students. In the next step the result is casted up to the
PersonClass. Final ly, the downcast selects those students that are also
employees. ((EmpClass)((PersonClass)((StudentClass)
(Person where age > 23)))) The same result can be obtained
with use of crosscast that allows us to omit the upcast to the PersonClass. ((EmpClass)
((StudentClass) (Person where age
> 23))) 9.6 Bidirectional
Pointers
The goal of bidirectional pointers
is to support automatic update of “twin” pointer objects that are
instances of a modeling construct commonly referred to as relationship (in the
Entity-Relationship model and in the ODMG standard) or association (in UML). Bidirectional pointers support only binary
relationships (associations) with no attributes (or association classes).
Before the definition of their semantics we outline the problem that was the
motivation for the construct. 9.6.1 The problem of binary associations
If the object model of an
application introduces a binary association, the update of one of its side
implies an appropriate update of its opposite side. Consider the following
example (from “Your First Tiny Object Base”; for the full example
see section 2.3). //(…) class
EmpClass extends PersonClass { instance Emp : { sal:integer; worksIn: ref Dept; } //the rest of
the definition } class
DeptClass { instance Dept : { dNbr: integer; dName: string; loc: string [1..*]; employs:
ref Emp [0..*]; } } //declaration of class instances collections Emp: EmpClass [0..*]; Dept: DeptClass
[0..*]; The classes define a binary
association implemented by two kinds of pointer objects: worksIn in EmpClass and
employs in DeptClass. The definition implicitly assumes that for a worksIn pointer there is a Dept object having a pointer
sub-object employs that points to
the Emp object having this worksIn pointer. Without automatic
support this implicit constraint must be managed by application programmers.
Such management implies the following requirements: a. Creation If a new Emp object is created the programmer is forced to create a
“reverse” pointer in the appropriate Dept object, as shown below: //create Emp object and insert employs pointer into
Dept //object (Dept where
dName = “Ads”) :<< employs( create
permanent Emp( //… , (Dept where dName = “Ads”) as worksIn)) ); (Note: create permanent returns a reference to the newly created
object.) b. Updating If an employee has to be moved to
another department, the worksIn
sub-object of the corresponding Emp
object must be updated. Together, the associated employs object has to be moved to the new Dept object. The example below illustrates the situation: //move employee to from Ads to Toys department //first update worksIn
pointer (Emp where
lName=“Doe”).worksIn :=
(Dept where
dName=“Toys”); //then move corresponding employs pointer (Dept where
dName = “Toys”) :< (Dept where
dName = “Ads”).employs where
Emp.lName = “Doe”; c. Deletion If an Emp object is to be deleted, the associated employs pointer must be removed, to avoid dangling pointers. In
ODRA this problem does not exist because an object store automatically
deletes all the pointer objects while the object pointed by them is being
deleted. The problem that is not solved is connected with the direct deletion
of an employs pointer. Semantically
this operation should be associated with the deletion of associated worksIn pointer. Because the declared
cardinality of the employs pointer
variable is [0..*] the type control system permits for the deletion of the employs objects. The information about
the associated worksIn object is
not accessible in this context to the type-checker and the mechanism is
unable to discover that the cardinality of worksIn ([1..1]) does not allow
for such deletion. 9.6.2 The solution – bidirectional
pointers
A bidirectional pointer object is
similar to an ordinary pointer object (with some exceptions). There is no
distinction in the SBQL syntax and semantics concerning navigation through
ordinary and bidirectional pointers. The syntax for update operation is also
the same but there are significant changes in the update semantics. The
difference is based on the assumption that bidirectional pointer always has
an associated “twin” pointer (that also must be bidirectional).
When a bidirectional pointer is created, an associated bidirectional pointer
is created too. If a bidirectional pointer is updated, then the appropriate
action is automatically taken on its counterpart. Bidirectional
pointer declaration Bidirectional pointers can be
declared only within classes. A declaration is similar to a declaration of an
ordinary pointer object with addition of the keyword reverse and the name of an associated pointer. For instance, a
declaration of a worksIn pointer in
the EmpClass as the bidirectional
pointer can be as follows: class
EmpClass extends PersonClass { instance Emp : { sal:integer; worksIn:
ref Dept reverse employs; } //the rest
of the definition } The above declaration requires the
existence of employs pointer in the DeptClass that also has to be declared as
bidirectional: class
DeptClass { instance Dept : { dNbr: integer; dName: string; loc: string [1..*]; employs:
ref Emp [0..*] reverse worksIn; } } The combination of those two declaration
allows to simplify the process of creating and updating bidirectional
pointers. a. Creating a bidirectional pointer //create Emp object (insertion of an employs pointer into a Dept object is
automatic) create permanent Emp( //… , (Dept where dName = “Ads”) as worksIn)); b. Updating a bidirectional
pointer //move an employee from Ads to Toys department
through updating worksIn (Emp where
lName = “Doe”).worksIn :=
(Dept where dName =
“Toys”); //the corresponding
employs pointer is moved
automatically e. Deleting a bidirectional pointer In our example the deletion of worksIn pointer is not allowed because
of it is declared as required (cardinality [1..1]). Thus if one tries to
delete associated employs pointer
the compiler raises a type error. |
Last modified: June 20, 2008 |
[1] Interfaces,
i.e. specifications of public properties of classes, are planned in the next
ODRA release.