Text not verified for kermeta 2 | |
---|---|
As Kermeta is object-oriented, it supports inheritance. Like its basis EMOF, it support both, simple and multiple inheritance.
abstract class Person { attribute name : string attribute lastName : string reference father : Male#children reference mother : Female#children } class Male inherits Person { reference children : oset Person[0..*]#father reference wife : Female[0..1]#husband } class Female inherits Person { reference children : oset Person[0..*]#mother reference husband : Male[0..1]#wife }
In this example, we define a simple model which represent simples family trees. Here, persons are defined by their name and last name. Each person have a father and mother and respectively might have children. The figure representing this example is Figure 2.1, “A simple family tree model”
As Kermeta is strongly typed, you cannot assign or pass something of the wrong type.
For example :
class A { } class SubA inherits A { } class AnotherSubA inherits A { } // ... var aA : A init SubA.new var aSubA : SubA aSubA := aA // doesn't work because not type safe
In this situation you must use one of the following methods : conditional assignment or asType
The conditional assignment
?=
allows to
assign only if the passed object is of the correct type.
If not of
the correct type,
the assigned value will simply be
Void
.
aSubA ?= aA // works var aAnotherSubA : AnotherSubA aAnotherSubA ?= aA // works too, but has been assigned Void and not the value
This still propose some control on the types and won't accept all kind of cast, for example, you cannot conditionaly assign if you don't have a common supertype.
class B { } // ... var aB : B aB ?= aA // doesn't work because not type safe
You can also use the operation
asType
to
cast your value. This one use a syntax which is shorter in some
situation, since you don't
have to create an intermediate variable
before passing the casted value to an operation.
However, the
drawback is that, if it fails to cast, then it will raise an
exception.
class C { operation needASubA(sa : SubA) : Void is do // do something end } // ... typical code using ?= var aC : C init C.new aC.needASubA(aA.asType(SubA))
In complement you can use the
isVoid
operation (available on every object) to test the result of a
conditional cast. Or you may use
the
isKindOf
or the
isInstanceOf
operations to test if the calling object has the same type of the
given class before
the
assignment/asType. The
isKindOf
operation returns
true
if the calling object
has exactly the same type than the given
class. The
isInstanceOf
operation returns
true
if the calling object has the exact type or one of its super type
than the given class.
class Parent { reference children : oset Child[0..*]#parent } class Child { reference parent : Parent#children
} class Male { } class GrandFather inherits Parent, Male { boolean healthy : Boolean }
The above example defines a class "GrandFather" that inherits at the same time the class "Parent", and the class "Male". Its graphical representation is shown in below figure.
In the following sample, when an operation is declared for the
first time (in the parent class
Person), it uses the
operation
keyword. Then, whenever you override it,
you will have to use the
method
keyword.
Note | |
---|---|
In the sample,Person and adopt are abstract, but this has no influence on the operation-method keywords rule, it would have been the same even if Person was not abstract or if adopt had a behavior in this class. |
abstract class Person { attribute name : String reference father : Male#children reference mother : Female#children operation adopt(child : Person) is abstract}
class Male inherits Person { reference wife : Female#husband reference children : oset Person[0..*]#father method adopt(child : Person) is do children.add(child) if not wife.children.contains(child) then child.father := self wife.adopt(child) end end}
class Female inherits Person { reference husband : Male#wife reference children : oset Person[0..*]#mother method adopt(child : Person) is do children.add(child) if not husband.children.contains(child) then child.mother := self husband.adopt(child) end end}
A MOF class can have operations but MOF does not provide any way to describe the behavior of these operations. Furthermore MOF does not provide any semantics neither for operation call nor for operation inheritance and redefinition. This section investigates how, while weaving actions into MOF, MOF semantics can be extended to support behavior definition and extension mechanisms provided by the action language. This implies answering several questions concerning redefinition and dispatch.
MOF does not specify the notion of overriding an operation because from a structural point of view it does not make any sense. To stick to MOF structure one can argue that redefinition should be forbidden in an executable MOF. This is the simplest solution as it also solves the problem of the dynamic dispatch since a simple static binding policy can be used.
However, operation redefinition is one of the key features of Object-Oriented (OO) languages. The OO paradigm has demonstrated that operation redefinition is a useful and powerful mechanism to define the behavior of objects and allow for variability. This would be very convenient to properly model dynamic semantic variation points existing in e.g. UML state-charts. For this reason we believe that an important feature of an executable MOF is to provide a precise behavior redefinition mechanism. The choice of the operation overriding mechanism must take into account the usual problem of redefinition such as method specialization and conflicting redefinitions related to multiple inheritance.
class A { operation m1() is do // Some behavior end // operation m2 is abstract operation m2() is abstract } class B inherits A { // method m1 inherits operation m1 from A method m1() is do // Behavior redefinition end method m2() is do // Implementation of the abstract method end } |
Table 2.1. Operation redefinition in Kermeta
Note | |
---|---|
Notice in that sample that method redefinition uses the
|
The issue of choosing semantics for operation overriding has been widely studied for the design of OO languages ( cf. M. Abadi and L. Cardelli, A theory of objects, Springer). However, OO languages have not adopted a unique solution to this problem. In this context, any language that defines an operation overriding mechanism should define precisely the solution it implements.
The simplest approach to overriding is to require that an overriding method has exactly the same signature as the overridden method. That is that both the type of the parameters and the return type of the operation should be invariant among the implementations of an operation. For the sake of simplicity this is the solution we have chosen for the current version of Kermeta.
However, this condition can be relaxed to allow method specialization, i.e. specialization on the types of parameters or/and return type of the operation. On one hand, the return type of the overriding method can be a sub-type of the return type of the overridden method. Method specialization is said to be covariant for the return types. On the other hand, the types of parameters of the overriding method might be super types of the parameters of the overridden methods. Method specialization is thus contravariant for the parameters.
In practice languages can allow method specialization only on the return type (this is the case of Java 1.5) or both on parameters and return type (this is the case of Eiffel). Among these solutions, we may choose a less restrictive policy then strict invariance for future versions of Kermeta in order to improve the static type checking of Kermeta programs.
Overloading is not allowed in Kermeta. This mechanism allows multiple operations taking different types of parameters to be defined with the same name. For each call, depending on the type of the actual parameters, the compiler or interpreter automatically calls the right one. This provides a convenient way for writing operations whose behaviors differ depending on the static type of the parameters. Overloading is extensively used is some functional languages such as Haskell and has been implemented in OO languages such as Java or C#. However it causes numerous problems in an OO context due to inheritance and even multiple inheritance in our case [REF?]. It is not implemented in some OO languages such as Eiffel for this reason, and that is why we chose to exclude overloading from Kermeta.
class A { method m(i : Integer) is do // [...] end method m(s : String) is do // this is not allowed in Kermeta !!! // [...] end }
This is also a classical problem that has been solved in several OO languages. There are mainly two kinds of conflicts when a class inherits features from several super-classes:
Several features with the same name might be inherited from different super classes causing a name clash.
Several implementations of a single operation could be inherited from different super classes.
There are two kinds of solutions to resolve these conflicts. The first one is to have an implicit resolution mechanism which chooses the method to inherit according to an arbitrary policy. The second one is to include in the language constructions that allow the programmer to explicitly resolve conflicts. In Eiffel, for instance, the programmer can rename features in order to avoid name clashes and can select the method to inherit if several redefinition of an operation are inherited from parent classes.
In the current version of Kermeta, we have chosen to include a minimal selection mechanism that allows the user to explicitly select the inherited method to override if several implementations of an operation are inherited. This mechanism does not allow resolving some name clashes and thus reject some ambiguous programs. For the future version of Kermeta we plan to include a more general mechanism such as traits proposed by Schärli et al. In any case we believe the conflict resolution mechanism should be explicit for the programmer.
class O { operation m() is abstract } class A inherits O { method m() is do // [...] end } class B inherits O { method m() is do // [...] end } class C inherits A, B { // "from" : an explicit selection of the//implementation to inherit method m() from A is do // [...] end } |
Table 2.2. Explicit selection of super operation in Kermeta