Next: , Previous: , Up: Top   [Contents][Index]


6 Interfaces and multiple inheritance

Since version 1.3 ooc introduces a kind of multiple inheritance with the help of interfaces. The idea behind is a bit a mix of the Java and C++ interfaces, but differs in the way of inheritance and the use.

6.1 What is an interface?

An interface is simply a collection of functions that describe the behavior of the Object in some aspect. The interface itself does not implement any functionality, it just defines what methods the Object must have, and behave according to it. In some design methods this is called a contract for the Object. The Object should implement its own implementation of the contract: this is called impementing the interface. In Java the interface is a pure abstract class without a data member, in C++ it is called a pure virtual class without constructor, destructor and data members.
The easiest way to define a group of functions is to collect some function pointers in a C struct. For example in pure C we would write:

struct DrawableInterafce
{
    void     (* draw)( Object );
    void     (* rotate)( Object, double );
    int      (* get_heigth)( Object );
    int      (* get_width)( Object );
};

This describes the behavior of a drawable Object. Any Object type that implements a DrawableInterafce can be asked for its dimensions and can be drawn and rotated.

6.1.1 Interfaces and inheritance

The use of interfaces provides a kind of multiple inheritance for ooc. While the classes can be inherited in a single inheritance chain (each class can have only one superclass), every class can implement as many interfaces as nedded. Since interfaces can be implemented by unrelated classes, it is a kind of multiple inheritance, like in Java.
In ooc interfaces are simply added to the virtual table, so you can reference and use them as any other virtual function of the class! This is very similar to the C++ implementation of an interface.
As a consequence, in ooc the interface implementation is inherited by the subclasses and can be overriden (like in C++, and unlike Java).
The good news is that by the help of interfaces ooc introduces multiple inheritance. The bad news is, that multiple inheritance calls for the dread problem of diamond inheritance (http://en.wikipedia.org/wiki/Diamond_problem).
Since an ANSI-C compiler has nothing to handle such a situation, we must avoid any possibility of a diamond inheritance, thus the interfaces themselves can not be inherited (other words: can not be extended) in ooc.
The following table summarizes the behaviour of interfaces in different languages:

oocC++Java
The implementation of the interface is inherited by subclassing a classyesyesno
Interface methods can be overriden in subclassesyesyesmust be
The interface itself can be inherited (a.k.a. extended in Java)noyesyes

6.1.2 Creating an interface

Creating an interface is very easy in ooc: you just simply deaclare its members in a publicly available header file. For example we create an interface for a drawable Object:
In drawable.h:

DeclareInterface( Drawable )

    void     (* draw        )( Object );
    void     (* rotate      )( Object, double );
    int      (* get_heigth  )( Object );
    int      (* get_width   )( Object );

EndOfInterface;

Please note, that the first parameter for each method in the interface is Object! This is, because any kind of a Class can implement the interface, so we can not limit the type of the Object for just a given class.
Each interafce has a unique identifier, an interface descriptor that must be statically allocated (in ROM on microcomputers). This can be done in drawable.c, or if we have many interfaces, collected them together in e.g. interfaces.c:

#include "drawable.h"

AllocateInterface( Drawable );

That’s it! We have done: the interface is declared and has a unique id: Drawable.
To minimize your work you can use the ooc tool to create the skeleton:

~$ ooc --new Drawable --template interface

6.1.3 Implementing an interface

To implement an interface for a class, you must do the followings:

  1. Add the interface to the class’s virtual table.
  2. Implement the interface methods for the class
  3. Initialize the virtual table with the interface method implementations
  4. Register the implemented interface(s) for the class
  5. Allocate the ClassTable using the interface register

So let’s say we implement the Drawable interface for the Cat class! In the followings we show only the steps necessary to implement the interface, other issues covered earlier are not repeated here!
Implelementing multiple interfaces for the class is the same, just repeat the necessary macros and lines! As an example, along with the Drawable interface we add the Pet interafce as well.

6.1.3.1 Adding the interface to the virtual table

In the public header (cat.h) locate the Virtuals, and add the interface with the Interface macro:

Virtuals( Cat, Animal )

    void     ( * miau )( Cat );

    Interface( Drawable );
    Interface( Pet );

EndOfVirtuals;

6.1.3.2 Implementing the interafce methods

The interface method impementations are static functions in the class implementation file. This is exactly the same aproach to the implementation of any virtual method. (Do not forget: an interface is just a collection of virtual functions in ooc.)
So in cat.c write:

static
void
cat_rotate( Cat self, double arcus )
{
    assert( ooc_isInstanceOf( self, Cat ) );

    // Rotate your cat here :-)
}

6.1.3.3 Initializing the virtual table

In the class initialization code you must assign the the implementation methods with the appropriate function pointers in the virtual table. Again, see the section about virtual functions.

static
void
Cat_initialize( Class this )
{
    CatVtable virtuals = (CatVtable) this->vtable;

    virtuals->Drawable.rotate = (void (*)(Object, double)) cat_rotate;
    // add the other methods as well ...
}

6.1.3.4 Registering the implemented interafces

There is a way to retrieve the implemented interfaces for any class, that is the ooc_get_interface() function. To let it work, we must register all implemented interfaces for the class. This is done in the class implementation file, preferably just right before the class allocation. So, in cat.c we register the Drawable and Pet interfaces for the Cat class:

InterfaceRegister( Cat )
{
    AddInterface( Cat, Drawable ),
    AddInterface( Cat, Pet )
};

Listen to the different syntax! Internally this is a table of structs, so you must end it with a semicolon, and use comma as the internal list separator. You must not put a comma after the last item in the list!
ooc_get_interface() scans this table, so you can get some increase in speed of average execution, if you place your most frequently used interfaces in the first positions.

6.1.3.5 Allocating the class with interfaces

The only thing remained is to let the ClassTable know about the interface register. So instead of AllocateClass use AllocateClassWithInterface:

AllocateClassWithInterface( Cat, Animal );

If you forget this step, ooc_get_interface() will always return NULL, since it will not know about the registered interfaces for the class.

With this step we have finished the implementation of an interface for a class.

6.1.4 Using an interface

When we need to invoke an interface method, simply call the virtual function via the function pointer in the virtual table. There can be two situation in practice: we may know the type of the object we have, or may not.

6.1.4.1 If the type of the Object is known

If we know the type of the object we have, and this type has implemented the interface that we need, then we can simply call the virtual function of the interface.

Cat mycat = cat_new();

// rotating mycat:
CatVirtual(mycat)->Drawable.rotate( (Object) mycat );

This is the fastest way to invoke an interface method, since this is a dereferencing of a function pointer only.

6.1.4.2 If the type of the Object is unknown

The main use of an interface is when we do not know the exact type of the object we have, but we want it to behave according its interface method. The ooc_get_interface() method can retrieve a given interface from any kind of Object. The ooc_get_interface() function returns a pointer to the interface methods of the class, and the desired method can be called via this function pointer.

void
rotate_an_animal( Animal self )
{
    Drawable drawable = ooc_get_interface( (Object) self, Drawable );

    if( drawable )
        drawable->rotate( (Object) self );
}

ooc_get_interface() returns NULL if the desired interface is not implemented for the object. If you use this function, always check for a NULL pointer, otherwise you will generate a segmentation fault, if the interface is not implemented.
In case you know that your object has implemented the desired interface, but you do not know the exact type of your object, you can use the ooc_get_interface_must_have() function instead. This will never return a NULL pointer, but throws an Exception if the object does not have the desired interface.

ooc_get_interface_must_have( (Object) self, Drawable )->rotate( (Object) self );

Note: the ooc_get_interface() function traveses the InterfaceRegister for the class and its superclasses, so it may be relative slow. If you use the interface for an object multiple times within a context, it is worth to retrieve the interface pointer only once for the object, and keep it around until needed.

6.2 Mixins

The mixins are interfaces that have default implementations and have their own data (http://en.wikipedia.org/wiki/Mixin).

A mixin does not know anything about the enclosing Object / Class!

From the user point of view a mixin looks identical to an interface! See Using an interface.

A mixin has its own data, but it can not be instantiated directly. It is always held by an Object (a carrier object).

A mixin has the following features:

6.2.1 Creating a mixin

The mixin is nothing else than an interface impementation plus a data structure. But this data structure is handled like a class, with very similar methods: there are initialization, finalization, construction, destruction and copy methods like for a normal Class, and an additional one: the ..._populate() method.
In this section we go through the steps creating a mixin.
To create the mixin skeleton for Flavour, just type:

~$ ooc --new Flavour --template mixin

Three files has been created:
The flavour.h is familiar, it looks like a normal interface declaration except it is declared with DeclareMixinInterface().
Put your interafce methods (function pointers, of course) into the declaration as needed.


There is a implement/flavour.h file, that contains the data member declaration for the mixin. Add your data members as needed.
These data fields will be mixed into the enclosing class’s object structure, and can be referenced via a FlavourData type pointer (interface name + Data suffix).


In the flavour.c file you will find the necessary AllocateMixin macro, and you must provide the method implementations for the mixin. The following methods must be implemented:

Flavour_initialize()

This is called only once, and is called automatically before the initialization of the first enclosing class.
Shared or global resources used by the mixin may be initialized here.

Flavour_finalize()

Called only once when ooc_finalize_all() is called.
Release the shared or global resources that were initialized by the mixin.

Flavour_constructor( Flavour flavour, FlavourData self )

Initializes the data fields of the mixin. All data fields are garanteed to be zero at entrance.
This method is called automatically before the enclosing class’s constructor is called.
If your data fields do not require any construction, you can leave this method empty.
The interface methods are set (and are overriden by the enclosing class), so they can be used here. (The first parameter.)
Note, that there are no constructor parameters! There is only "default constructor" for mixins. If you need some values or construction depending on the enclosing object then implement a setter or constructor method in the interface itself and call it from the enclosing class’s constructor!

Flavour_destructor( Flavour flavour, FlavourData self )

Destroys the data fields of the mixin.
This method is called automatically after the enclosing class’s destructor, and the normal rules within a desctructor must be applied here as well!
If your data fields do not require any destruction, you can leave this method empty. The interface methods are set (and are overriden by the enclosing class), so they can be used here. (The first parameter.)

Flavour_copy( Flavour flavour, FlavourData self, const FlavourData from )

Copy constructor. This is very similar to the copy constructor used for Objects! Must return: OOC_COPY_DEFAULT, OOC_COPY_DONE or OOC_NO_COPY, and the same rules apply!
This method is called automatically before calling the enclosing class’s copy constructor.
The interface methods are set (and are overriden by the enclosing class), so they can be used here. (The first parameter.)

Flavour_populate( Flavour flavour )

Populates the interface method table with function pointers to the default mixin method implementations.
This method is called automatically before calling the enclosing class’s initialization method.


The mixin interface methods’ implementation must follow an important rule! Since the mixin does not know where its data is located, the mixin’s data must be retrieved first!
The only thing that a mixin method knows is the carrier Object (always the first parameter for an interface method!). Retrieving the mixin’s data fields can be done with the ooc_get_mixin_data() function.
Therefore a typical mixin interface method starts with the followings:

static
void
flavour_set( Object carrier, const char * flavour_name )
{
    FlavourData self = ooc_get_mixin_data( carrier, Flavour );
 
    self->name = flavour_name;
}

6.2.2 Implementing a mixin by a carrier class

The implementation of a mixin for a class is almost the same as the implementation of an interface, with few additional tasks.
The steps to implement some Flavour for the IceCream class are:

6.2.3 How mixins work?

Mixins work like a superclass for the enclosing class: you do not have to care with it, ooc does everything automatically for you:

This is the same behavior of a superclass, you can treat the implemented mixin as a superclass of the enclosing class as well, except you can not cast the types (Mixins do not have type information at all.), and you must access it as an interface via its interface methods.


However, mixins have some drawbacks!
First: it exposes all mixin data members to the enclosing (carrier) class: the enclosing class can acces the mixin’s data directly, and it is up to the programmer’s intelligence not to do bad thigs. :-(
Second: using interafces and mixins frequently is a very elegant design, but be aware that in ooc it is a bit expensive! Since a C compiler gives no support for such implementations, every lookup, check and conversion is done in run-time, thus much slower than in C++ for example.


Next: , Previous: , Up: Top   [Contents][Index]