forked from enlightenment/www-content
Wiki page eo-inherit.md changed with summary [created] by Xavi Artigas
This commit is contained in:
parent
45f6a61745
commit
ced687f6c3
|
@ -0,0 +1,280 @@
|
||||||
|
---
|
||||||
|
~~Title: Class Inheritance with Eolian~~
|
||||||
|
---
|
||||||
|
|
||||||
|
# Class Inheritance with Eolian #
|
||||||
|
|
||||||
|
The [Creating New Classes](eo-classes.md) tutorial explained how to define new classes using Eolian. New classes, though, don't need to start from scratch. In fact, it is common practice in *Object Oriented Programming* to (OOP) extend the functionality of an existing class by *inheriting* from it and creating a new one.
|
||||||
|
|
||||||
|
This tutorial shows how to inherit from a class in Eolian. It also describes *interfaces*, a particular kind of class which allows describing common functionality across different classes.
|
||||||
|
|
||||||
|
## Prerequisites ##
|
||||||
|
|
||||||
|
* This tutorial builds on top of the ``Example.Rectangle`` class developed in [Creating New Classes with Eolian](eo-classes.md).
|
||||||
|
* The [Hello World](hello-world.md) tutorial explains how to write an application using the EFL.
|
||||||
|
|
||||||
|
## Step One: Creating a Derived Class ##
|
||||||
|
|
||||||
|
Copy all the ``example_rectangle.*`` files you created in the [Creating New Classes with Eolian](eo-classes.md) tutorial. There should be 4 of them: The Eolian file (``.eo``), the implementation file (``.c``) and two autogenerated files (``.eo.h`` and ``.eo.c``). Also copy the main file (``eo_classes_main.c``) and rename it to ``eo_inherit_main.c``, for consistency with the name of this tutorial.
|
||||||
|
|
||||||
|
Now you will create a new class, named ``Example.Square`` which will inherit from ``Example.rectangle``. The theory states that squares are a particular kind of rectangles in which the width and the height are equal. Therefore, the ``Example.Square`` class will *override* ``Example.Rectangle``'s ``width`` and ``height`` setters to ensure that those two variables have always the same value.
|
||||||
|
|
||||||
|
Start describing your new class with a new Eolian file named ``example_square.eo``:
|
||||||
|
|
||||||
|
```
|
||||||
|
class Example.Square (Example.Rectangle) {
|
||||||
|
implements {
|
||||||
|
Example.Rectangle.width {set;}
|
||||||
|
Example.Rectangle.height {set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, it is derived from ``Example.Rectangle``. Regular classes must derive from the ``Efl.Object``, but in this case, you get that automatically, since ``Example.Rectangle`` already derives from ``Efl.Object``.
|
||||||
|
|
||||||
|
You can also notice that this class does not provide any new method or property (there is no ``methods`` block). It only implements two properties, which currently belong to its parent class (``Example.Rectangle.width`` and ``Example.Rectangle.height``). Furthermore, only the setters for these properties are implemented: Reads of these properties will be routed to the getter in the parent class.
|
||||||
|
|
||||||
|
Next, turn the Eolian description into C files with the ``eolian_gen`` command (as seen in the previous tutorial). Be careful, though, there is a new parameter in use in this example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
eolian_gen -gchi example_square.eo -I .
|
||||||
|
```
|
||||||
|
|
||||||
|
The ``-I`` parameter tells ``eolian_gen`` where to look for other Eolian files, in case it needs the description of a class it does not know about. In this tutorial, ``Example.Square`` needs the description of ``Example.Rectangle`` which resides in the ``example_rectangle.eo`` file. This file is in the same folder as ``example_square.eo``, the current folder, therefore, the final command requires a ``-I .`` (The dot indicates the current folder).
|
||||||
|
|
||||||
|
This will create the boilerplate files (``.eo.h`` and ``.eo.c``) and the implementation file which you will fill in the next step.
|
||||||
|
|
||||||
|
## Step Two: Implementing the Derived Class ##
|
||||||
|
|
||||||
|
Edit the implementation file ``example_square.c``. It should contain:
|
||||||
|
|
||||||
|
* An empty structure ``Example_Square_Data``. It will remain empty, because squares do not add any additional data to rectangles.
|
||||||
|
* A method called ``_example_square_example_rectangle_width_set()``. This is the setter for the ``width`` property, inherited from the parent ``Example.Rectangle`` class. The name of the method contains all the ancestry information. Don't worry, though, you will not be calling this method directly.
|
||||||
|
* A method called ``_example_square_example_rectangle_height_set()``. As above, this is the setter for the ``height`` property.
|
||||||
|
|
||||||
|
The implementation of these methods requires calling the parent class, therefore, you need to include the parent's class header file. Add a line after the first block of ``#include``s in the file:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "example_rectangle.eo.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
Time to think now about the implementation of these setters. They both need to set the ``width`` and ``height`` internal variables to the same value. But these variables are private to ``Example.Rectangle``, so ``Example.Square`` has no direct access to them. They can be accessed through the setters from ``Example.Rectangle``, though. Only care has to be taken not to end up calling the setter from ``Example.Square`` currently being implemented, or an infinite loop would be created.
|
||||||
|
|
||||||
|
In EFL, when you need to call a method from your parent instead of your overridden version, you can use ``efl_super()``. Its first parameter is the object and the second is *the class whose parent you want to call*. Write these implementations for the setters and it all will become apparent:
|
||||||
|
|
||||||
|
```c
|
||||||
|
EOLIAN static void
|
||||||
|
_example_square_example_rectangle_width_set(Eo *obj, Example_Square_Data *pd EINA_UNUSED, int width)
|
||||||
|
{
|
||||||
|
example_rectangle_width_set(efl_super(obj, EXAMPLE_SQUARE_CLASS), width);
|
||||||
|
example_rectangle_height_set(efl_super(obj, EXAMPLE_SQUARE_CLASS), width);
|
||||||
|
}
|
||||||
|
|
||||||
|
EOLIAN static void
|
||||||
|
_example_square_example_rectangle_height_set(Eo *obj, Example_Square_Data *pd EINA_UNUSED, int height)
|
||||||
|
{
|
||||||
|
example_rectangle_width_set(efl_super(obj, EXAMPLE_SQUARE_CLASS), height);
|
||||||
|
example_rectangle_height_set(efl_super(obj, EXAMPLE_SQUARE_CLASS), height);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Things worth noticing:
|
||||||
|
|
||||||
|
* The ``width`` and ``height`` variables are set through the regular setters you already used in the previous tutorial (``example_rectangle_width_set()`` and ``example_rectangle_height_set()``).
|
||||||
|
* The object being passed to the setters, though, is the output of ``efl_super()``.
|
||||||
|
* You want to call the parent of ``Example.Square``, so the second parameter to ``efl_supper()`` is ``EXAMPLE_SQUARE_CLASS``.
|
||||||
|
* These setters do not use the ``Example_Square_Data`` private data (that structure is actually empty, as seen above), so ``EINA_UNUSED`` is used to avoid compiler warnings.
|
||||||
|
|
||||||
|
The ``efl_super()`` method can also be used to access older ancestors of your class, but that's an advanced scenario not required in this tutorial.
|
||||||
|
|
||||||
|
Having written these setters, your derived ``Example.Square`` class is finished. The next step adds code that uses it.
|
||||||
|
|
||||||
|
## Step Three: Using the Derived Class ##
|
||||||
|
|
||||||
|
Open up ``eo_inherit_main.c`` and start by adding the include for ``Example.Square``:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "example_square.eo.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add another method to instantiate your new class, right after ``_rect_create()``:
|
||||||
|
|
||||||
|
```c
|
||||||
|
Example_Square *
|
||||||
|
_square_create()
|
||||||
|
{
|
||||||
|
Example_Square *square;
|
||||||
|
|
||||||
|
square = efl_add(EXAMPLE_SQUARE_CLASS, NULL,
|
||||||
|
efl_name_set(efl_added, "Square"),
|
||||||
|
example_rectangle_width_set(efl_added, 7));
|
||||||
|
|
||||||
|
return square;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Observe how the side of the rectangle is set with ``example_rectangle_width_set()``. You could also set the ``height``, since both setters have the same effect on a square.
|
||||||
|
|
||||||
|
Back in the main function, declare a variable to hold your new object:
|
||||||
|
|
||||||
|
```c
|
||||||
|
Eo *rectangle, *square;
|
||||||
|
```
|
||||||
|
|
||||||
|
Now call ``_square_create()`` and print some information (right after doing the same thing for the rectangle object):
|
||||||
|
|
||||||
|
```c
|
||||||
|
square = _square_create();
|
||||||
|
|
||||||
|
printf("Square is %dx%d, area is %d\n",
|
||||||
|
example_rectangle_width_get(square),
|
||||||
|
example_rectangle_height_get(square),
|
||||||
|
example_rectangle_area(square));
|
||||||
|
|
||||||
|
efl_unref(square);
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice how you only used ``Example.Rectangle`` methods here, because ``Example.Square`` inherits from it, and all methods that work on a rectangle also work on a square.
|
||||||
|
|
||||||
|
Also, remember to dispose of your objects using ``efl_unref()`` if you do not give them a parent in ``efl_add()``.
|
||||||
|
|
||||||
|
The main program (``eo_inherit_main.c``) should now look like this:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define EFL_EO_API_SUPPORT 1
|
||||||
|
#define EFL_BETA_API_SUPPORT 1
|
||||||
|
|
||||||
|
#include <Eina.h>
|
||||||
|
#include <Efl_Core.h>
|
||||||
|
#include "example_rectangle.eo.h"
|
||||||
|
#include "example_square.eo.h"
|
||||||
|
|
||||||
|
Example_Rectangle *
|
||||||
|
_rect_create()
|
||||||
|
{
|
||||||
|
Example_Rectangle *rectangle;
|
||||||
|
|
||||||
|
rectangle = efl_add(EXAMPLE_RECTANGLE_CLASS, NULL,
|
||||||
|
efl_name_set(efl_added, "Rectangle"),
|
||||||
|
example_rectangle_width_set(efl_added, 5),
|
||||||
|
example_rectangle_height_set(efl_added, 10));
|
||||||
|
|
||||||
|
return rectangle;
|
||||||
|
}
|
||||||
|
|
||||||
|
Example_Square *
|
||||||
|
_square_create()
|
||||||
|
{
|
||||||
|
Example_Square *square;
|
||||||
|
|
||||||
|
square = efl_add(EXAMPLE_SQUARE_CLASS, NULL,
|
||||||
|
efl_name_set(efl_added, "Square"),
|
||||||
|
example_rectangle_width_set(efl_added, 7));
|
||||||
|
|
||||||
|
return square;
|
||||||
|
}
|
||||||
|
|
||||||
|
EAPI_MAIN void
|
||||||
|
efl_main(void *data EINA_UNUSED, const Efl_Event *ev EINA_UNUSED)
|
||||||
|
{
|
||||||
|
Eo *rectangle, *square;
|
||||||
|
|
||||||
|
rectangle = _rect_create();
|
||||||
|
|
||||||
|
printf("Rectangle is %dx%d, area is %d\n",
|
||||||
|
example_rectangle_width_get(rectangle),
|
||||||
|
example_rectangle_height_get(rectangle),
|
||||||
|
example_rectangle_area(rectangle));
|
||||||
|
|
||||||
|
efl_unref(rectangle);
|
||||||
|
|
||||||
|
square = _square_create();
|
||||||
|
|
||||||
|
printf("Square is %dx%d, area is %d\n",
|
||||||
|
example_rectangle_width_get(square),
|
||||||
|
example_rectangle_height_get(square),
|
||||||
|
example_rectangle_area(square));
|
||||||
|
|
||||||
|
efl_unref(square);
|
||||||
|
|
||||||
|
efl_exit(0);
|
||||||
|
}
|
||||||
|
EFL_MAIN()
|
||||||
|
```
|
||||||
|
|
||||||
|
If you run it, you should get this on your terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Rectangle is 5x10, area is 50
|
||||||
|
Square is 7x7, area is 49
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step Four: Accessing the Parent's Private Data ##
|
||||||
|
|
||||||
|
The above implementation for ``Example.Square``'s setters works because ``Example.Rectangle`` has public setters. That is, even though ``width`` and ``height`` are private variables visible only to ``Example.Rectangle`` they can be accessed by anyone through their setters and getters.
|
||||||
|
|
||||||
|
The last step in this tutorial shows how a derived class can access private data from its parent, which is also a common operation in OOP.
|
||||||
|
|
||||||
|
The first thing done in the implementation file ``example_rectangle.c`` is to define the ``Example_Rectangle_Data`` structure, which is therefore only accessible from that file. If ``Example.Square`` has to have access to this structure, it has to be defined in a common header.
|
||||||
|
|
||||||
|
Create an ``example_rectangle_private.h`` file and move the structure there:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int width, height;
|
||||||
|
} Example_Rectangle_Data;
|
||||||
|
```
|
||||||
|
|
||||||
|
In ``example_rectangle.c``, replace the structure with an include :
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "example_rectangle_private.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally include the header *also* from ``example_square.c``. At this point, the implementation for ``Example.Square`` can understand the private data of its parent ``Example.Rectangle``. You only need to retrieve a pointer to that data, using ``efl_data_scope_get()``. This is how your square setters should look now:
|
||||||
|
|
||||||
|
```c
|
||||||
|
EOLIAN static void
|
||||||
|
_example_square_example_rectangle_width_set(Eo *obj, Example_Square_Data *pd EINA_UNUSED, int width)
|
||||||
|
{
|
||||||
|
Example_Rectangle_Data *rect_pd = efl_data_scope_get(obj, EXAMPLE_RECTANGLE_CLASS);
|
||||||
|
rect_pd->width = width;
|
||||||
|
rect_pd->height = width;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And likewise for the ``height`` setter.
|
||||||
|
|
||||||
|
Notice how the first parameter to ``efl_data_scope_get()`` is the object for which you want to retrieve the private data, and the second parameter is the *ancestor class*. You can retrieve the private data for any class, as long as it belongs to your hierarchy.
|
||||||
|
|
||||||
|
> **NOTE:**
|
||||||
|
> For performance reasons, no runtime check is performed to ensure that the requested class actually belongs to your ancestry. If you want to avoid *undefined behavior* use ``efl_data_scope_safe_get()``.
|
||||||
|
|
||||||
|
Once you have the pointer to the private data of ``Example.Rectangle`` you can write to both ``width`` and ``height`` as you were doing before.
|
||||||
|
|
||||||
|
Finally, if you need to keep this private data pointer alive for a long time, it is worth reading about ``efl_data_ref()`` and ``efl_data_unref()``. These methods will make sure that the enclosing object is not destroyed until you are done working with its private data.
|
||||||
|
|
||||||
|
## Step Five: Per-Object Method Override ##
|
||||||
|
|
||||||
|
One final function worth knowing is ``efl_object_override()``. It allows changing some method's implementation for a particular object, just like derived classes do, but on a single object.
|
||||||
|
|
||||||
|
Its use case is a bit advanced so it will not be shown in this tutorial. It is related to class inheritance, though, so it deserves being mentioned.
|
||||||
|
|
||||||
|
## Summary ##
|
||||||
|
|
||||||
|
This tutorial has taught you:
|
||||||
|
|
||||||
|
* **Derived classes** can be created with Eolian.
|
||||||
|
* **Methods overridden by the derived class** are automatically called when a derived class object is used.
|
||||||
|
* **Public data** of the parent class can be accessed through its **public accessors**.
|
||||||
|
* **Private data** of the parent class can be accessed through ``efl_data_scope_get()``.
|
||||||
|
* **Method implementations on individual objects** can be overridden using ``efl_object_override()``.
|
||||||
|
|
||||||
|
## Further Reading ##
|
||||||
|
|
||||||
|
[Creating New Classes](eo-classes.md)
|
||||||
|
: Teaches the basis of class creation with Eo
|
||||||
|
|
||||||
|
[Multiple Inheritance](eo-multiinherit.md)
|
||||||
|
: Moves forward and explains how to inherit from more than one class at a time
|
Loading…
Reference in New Issue