2.9.  Inheritance

[Warning]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.

2.9.1.  Using simple 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”

A simple family tree model

Figure 2.1. A simple family tree model


2.9.2. Cast

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

2.9.2.1. Casting using conditional assignment

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
        	

2.9.2.2. Casting using asType

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))
        	

2.9.2.3. Useful functions when casting

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.

2.9.3. Using multiple inheritance

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.

: multiple inheritance

Figure 2.2. : multiple inheritance


2.9.4. Overriding behavior with methods

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]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}

2.9.5. Some limitations

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.

2.9.5.1. Operation redefinition

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]Note

Notice in that sample that method redefinition uses the method keyword instead of operation.

2.9.5.2. Operation specialization

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.

2.9.5.3. Operation overloading

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
}

2.9.5.4. Conflicts related to multiple inheritance

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