WWW-www.enlightenment.org pushed a commit to branch master. http://git.enlightenment.org/website/www-content.git/commit/?id=42f55062eae8ee921e4a86495c81dc58e559ba04
commit 42f55062eae8ee921e4a86495c81dc58e559ba04 Author: Xavi Artigas <[email protected]> Date: Wed Nov 22 06:03:05 2017 -0800 Wiki page eo-multiinherit.md changed with summary [created] by Xavi Artigas --- pages/develop/tutorial/c/eo-multiinherit.md.txt | 514 ++++++++++++++++++++++++ 1 file changed, 514 insertions(+) diff --git a/pages/develop/tutorial/c/eo-multiinherit.md.txt b/pages/develop/tutorial/c/eo-multiinherit.md.txt new file mode 100644 index 000000000..4bcc02b15 --- /dev/null +++ b/pages/develop/tutorial/c/eo-multiinherit.md.txt @@ -0,0 +1,514 @@ +--- +~~Title: Multiple Class Inheritance with Eolian~~ +--- + +# Multiple Class Inheritance with Eolian # + +The [previous tutorial](eo-inherit.md) showed how a new class can be created by inheriting from an already existing *base class*. Like many other programming languages, Eolian also allows inheriting from more than one place at the same time, with certain restrictions. + +This tutorial explains the special kinds of classes that Eolian offers to allow trouble-free multiple inheritance: *interfaces* and *mixins*. + +## Prerequisites ## + +* This tutorial builds on top of the classes developed in [Class Inheritance with Eolian](eo-inherit.md). + +## The Risks of Multiple Class Inheritance ## + +Allowing a class to have more than one parent has some caveats, like the well-known *diamond problem*: suppose a class ``Parent`` has a method, overridden in different ways by its two children ``Child1`` and ``Child2``. Now a new class ``Grandchild`` inherits from both ``Child1`` and ``Child2`` and calls that method. Which implementation should be actually called? + +C++, for example, allows you to create such hierarchies, but forces you to specify which implementation you want to use in the ambiguous cases. Most languages, though, solve the diamond problem by imposing restrictions on the classes from which you can inherit. These restrictions remove the possibility of ambiguous hierarchies and result in arguably cleaner code. + +Eolian (and [other languages](https://en.wikipedia.org/wiki/Mixin)) define two new kinds of classes, *interfaces* and *mixins*, and imposes inheritance rules: + +1. You can only *inherit* from one *regular* class, which must be the first one in the inheritance list. + +2. You can *implement* as many *interfaces* as you want. + +3. You can *include* as many *mixins* as you want. + +*Inherit*, *implement* and *include* all mean *use functionality from a parent class*, but using a different word for each kind of parent class helps keep ambiguity to a minimum. + +Interfaces are like classes but they only define methods and contain no implementation. Mixins are classes which contain implementations, but they cannot be inherited from, only included. Neither is instantiable on its own, they are meant to be implemented or included. + +The following steps will clarify these concepts. + +## Step One: Copying the Code from the Previous Tutorial ## + +This tutorial builds on top of the previous one ([Class Inheritance with Eolian](eo-inherit.md)), so begin by creating a copy of all your code to a new folder. + +Then rename the main file ``eo_inherit_main.c`` to ``eo_multiinherit_main.c`` to keep consistency with this tutorial's name. + +## Step Two: Merging the Includes ## + +As you have already learned, the header file for each new class needs to be included in every file that uses that class. In this example you are going to add several classes and objects to the hierarchy created in the previous tutorial, which means a lot of new header files need to be kept track of. + +A common practice to simplify this situation is to include all header files from a new file and then only use the new file. Therefore, create a new file and call it ``eo_multiinherit.h``: + +```c +#include "example_rectangle.eo.h" +#include "example_square.eo.h" +``` + +And then use: + +```c +#include "eo_multiinherit.h" +``` + +Instead of ``example_rectangle.eo.h`` and ``example_square.eo.h`` in all implementation files. + +You can build and run your program now to check that everything still works as expected. + +## Step Three: Defining an Interface ## + +Interfaces are like classes but do not contain any implementation; they only contain method definitions. When a class *implements* an interface (another way of saying that the class inherits from the interface) it is agreeing to provide the implementation for the missing methods. In this way, the interface methods can be called on different unrelated classes, as long as they all implement the interface. + +You are now going to define a new interface in Eolian, which will be implemented by the ``Example.Rectangle`` class. It does not make much sense to use an interface when only one class is implementing it, but the next steps will define more classes. + +The interface will be called ``Example.Shape`` and will only define the method ``area()`` which previously was part of the ``Example.Rectangle`` class. + +Create the ``example_shape.eo`` file and write its contents: + +``` +interface Example.Shape { + methods { + area { + params { + } + return: int; + } + } +} +``` + +As you can see, the interface is declared with the keyword ``interface`` instead of ``class`` as you did for the other classes. The rest of the file should already be familiar to you: It contains a ``methods`` block which defines only one method called ``area()``. + +Now turn this Eolian file into C code with ``eolian_gen``. It is worth noting that, since interfaces do not contain implementations, you do not need to generate an implementation file (i.e., you could use the ``-gch`` switch instead of ``-gchi`` to avoid generating ``example_shape.c``). + +However, you would need to remember to include in your build the boilerplate C file ``example_shape.eo.c`` instead of the usual implementation file ``example_shape.c``, so, for consistency, generate the implementation file. It will be mostly empty anyway, and just include the boilerplate C file. + +```bash +eolian_gen -gchi example_shape.eo +``` + +Now edit the includes file ``eo_multiinherit.h`` and add the newly generated ``example_shape.eo.h`` after the other includes: + +```c +#include "example_shape.eo.h" +``` + +## Step Four: Implementing the Interface ## + +Now tell ``Example.Rectangle`` that it will be implementing the new ``Example.Shape`` interface. Open the ``example_rectangle.eo`` file and: + +* Add ``Example.Shape`` after ``Efl.Object`` (separated with a comma) in the list of parents at the top of the file. + +* Remove all lines in the ``area { ... }`` block. + +* Add a new block after the closing brace of the ``methods`` block: + + ``` + implements { + Example.Shape.area; + } + ``` + +This tells Eolian that the ``Example.Rectangle`` class is going to implement the ``area()`` method from the ``Example.Shape`` interface, instead of providing it own. + +The whole file should look like this: + +``` +class Example.Rectangle (Efl.Object, Example.Shape) { + methods { + @property width { + set { + } + get { + } + values { + width: int; + } + } + @property height { + set { + } + get { + } + values { + height: int; + } + } + } + implements { + Example.Shape.area; + } +} +``` + +Run ``eolian_gen`` again to obtain the C code: + +```bash +eolian_gen -gchi example_rectangle.eo -I . +``` + +Mind the ``-I`` switch so it can find the new class you are including now! + +``eolian_gen`` will **add** to your already existing implementation file ``example_rectangle.c`` any method that is missing, but it **will not** remove already existing methods which are not needed anymore. + +This means that in the implementation file you will now find: + +* An empty method where you must put the implementation for ``Example.Shape.area()``: ``_example_rectangle_example_shape_area()``. + +* Your previous implementation of the ``Example.Rectangle.area()`` method: ``_example_rectangle_area()``. + +Simply move the one-line implementation (``return pd->width * pd->height;``) from the old method to the new one, and remove completely the old one. + +It might look like not much has changed, but the area method is now available through an interface, which means that all classes implementing it (including ``Example.Rectangle`` and its descendants) can be handled in a similar manner. + +## Step Five: Using the Interface ## + +Since you removed the ``area()`` method from ``Example.Rectangle``, the main program will not work anymore. However, since ``Example.Rectangle`` implements the ``Example.Shape`` interface now, you can use its methods, including ``area()``. + +Replace both uses of ``example_rectangle_area()`` with ``example_shape_area()``. + +> **NOTE:** +> At this point the ``_rectangle_create()`` and ``_square_create()`` methods can return their respective types or an ``Example_Shape *`` (or even an ``Eo *`` as seen in the [Introduction to Eo](eo-intro) tutorial). + +Now build and run the program. Remember to include the new source file ``example_shape.c``: + +```bash +gcc eo_multiinherit_main.c example_rectangle.c example_square.c example_shape.c `pkg-config --cflags --libs elementary` +``` + +Upon execution, you should get on your terminal the same message you got at the end of the previous tutorial: + +``` +Rectangle is 5x10, area is 50 +Square is 7x7, area is 49 +``` + +## Step Six: Adding Another Class ## + +Now, to finally show the usefulness of the interface, you will add another class, unrelated to ``Example.Rectangle`` or ``Example.Square``, but implementing the same ``Example.Shape`` interface. + +Start by creating a new Eolian file called ``example_circle.eo``: + +``` +class Example.Circle (Efl.Object, Example.Shape) { + methods { + @property radius { + set { + } + get { + } + values { + radius: int; + } + } + } + implements { + Example.Shape.area; + } +} +``` + +It looks a lot like ``example_rectangle.eo`` but it defines a class named ``Example.Circle``, which has a ``radius`` property (instead of ``width`` and ``height``) and also implements the ``area()`` method from the ``Example.Shape`` interface. + +Now turn this file into C code with ``eolian_gen``: + +```bash +eolian_gen -gchi example_circle.eo -I . +``` + +Add the new header file ``example_circle.eo.h`` to ``eo_multiinherit.h``: + +```c +#include "example_circle.eo.h" +``` + +And then add the implementation for the new class. Edit ``example_circle.c`` and: + +* Replace the automatically generated ``#include "example_circle.eo.h"`` with ``#include "eo_multiinherit.h"`` + +* Inside the ``Example_Circle_Data`` structure add a variable to hold the radius of the circle: ``int radius;`` + +* Add the implementation for the setter: ``pd->radius = radius;`` + +* Add the implementation for the getter: ``return pd->radius;`` + +* Add the implementation for the ``area()`` interface method: ``return (int)(pd->radius * pd->radius * 3.14159f);`` + +After adding some ``EINA_UNUSED`` to the unused method parameters, the implementation file should look like this: + +```c +#define EFL_BETA_API_SUPPORT +#include <Eo.h> +#include "eo_multiinherit.h" + +typedef struct +{ + int radius; +} Example_Circle_Data; + +EOLIAN static void +_example_circle_radius_set(Eo *obj EINA_UNUSED, Example_Circle_Data *pd, int radius) +{ + pd->radius = radius; +} + +EOLIAN static int +_example_circle_radius_get(Eo *obj EINA_UNUSED , Example_Circle_Data *pd) +{ + return pd->radius; +} + +EOLIAN static int +_example_circle_example_shape_area(Eo *obj EINA_UNUSED, Example_Circle_Data *pd) +{ + return (int)(pd->radius * pd->radius * 3.14159f); +} + +#include "example_circle.eo.c" +``` + +The next step instantiates this new class to check that everything works as expected. + +## Step Seven: Using the New Class ## + +Start by adding the method that will instantiate the new class. You should already be familiar with all the required bits: + +```c +Example_Circle * +_circle_create() +{ + Example_Circle *circle; + + circle = efl_add(EXAMPLE_CIRCLE_CLASS, NULL, + efl_name_set(efl_added, "Circle"), + example_circle_radius_set(efl_added, 5)); + + return circle; +} +``` + +Next, you will make use of the fact that all instantiated shapes implement the ``Example.Shape`` interface and remove the duplicated ``printf()`` calls. Create a new method that will take care of printing: + +```c +void +_shape_print(Example_Shape *shape) +{ + printf("Shape named %s has area %d\n", + efl_name_get(shape), example_shape_area(shape)); +} +``` + +Notice how it receives an ``Example_Shape *`` and uses methods from that interface (and from ``Efl_Object``, like ``efl_name_get()``). This method will not be able to print the ``width`` and ``height`` of the rectangles as before anymore, since it now receives shapes which might not be a rectangle at all. + +Now replace the whole content of ``efl_main()``: + +```c + Eo *shape; + + shape = _circle_create(); + _shape_print(shape); + efl_unref(shape); + + shape = _rectangle_create(); + _shape_print(shape); + efl_unref(shape); + + shape = _square_create(); + _shape_print(shape); + efl_unref(shape); + + efl_exit(0); +``` + +As you can see, handling the different shapes is easier now through the common interface. + +If you build and run the program (remember to include ``example_circle.c`` in the mix): + +```bash +gcc eo_multiinherit_main.c example_rectangle.c example_square.c example_shape.c example_circle.c `pkg-config --cflags --libs elementary` +``` + +You should get this on the terminal: + +``` +Shape named Circle has area 78 +Shape named Rectangle has area 50 +Shape named Square has area 49 +``` + +This concludes the part regarding interfaces. The next steps exemplify the usage of mixins. + +## Step Eight: Defining a Mixin ## + +Mixins are meant to provide units of functionality, ideally small and independent of any other class. You can add new functionality to your class simply by including different mixins in it. + +But you cannot inherit from a mixin, meaning that mixins cannot be the first element in the inheritance list of a class (the list in the parentheses after the class name in an Eolian file). This ensures that the ambiguous diamond pattern described in the introduction to this tutorial does not happen. + +You will create a mixin called ``Example.Colored`` providing coloring facilities to your classes. Any class including this mixin will have a ``color`` property added to it, complete, with setter and getter. Without further modification of your class, since the mixin provides all the implementation. + +You will only be adding the mixin to some of the classes, to show how you can mix and match classes and mixins to suit your use case. + +Start by creating yet another Eo file: ``example_colored.eo``: + +``` +mixin Example.Colored { + methods { + @property color { + get { + } + set { + } + values { + red: int; + green: int; + blue: int; + } + } + } +} +``` + +As you can see, it looks like a regular class named ``Example.Colored``, providing a single property called ``color`` composed of ``red``, ``green`` and ``blue`` integer values. Nothing special besides the keyword ``mixin`` used instead of ``class``. + +Turn into C code using the usual ``eolian_gen`` command: + +```bash +eolian_gen -gchi example_colored.eo +``` + +As you did before, edit the includes file ``eo_multiinherit.h`` and add the newly generated ``example_colored.eo.h`` after the other includes: + +```c +#include "example_colored.eo.h" +``` + +## Step Nine: Implementing the Mixin ## + +The implementation of this mixin is simple, just a standard setter and getter for the property. Open ``example_colored.c`` and: + +* Replace the ``#include "example_colored.eo.h`` with ``#include "eo_multiinherit.h"`` + +* Add ``int red, green, blue;`` inside the structure ``Example_Colored_Data``. + +* Implement the setter as: ``pd->red = red; pd->green = green; pd->blue = blue; `` + +* Implement the getter as: ``*red = pd->red; *green = pd->green; *blue = pd->blue;``. Add checks for ``NULL`` pointers for added security. + +The full ``example_colored.c`` file should look like this: + +```c +#define EFL_BETA_API_SUPPORT +#include <Eo.h> +#include "eo_multiinherit.h" + +typedef struct +{ + int red, green, blue; +} Example_Colored_Data; + +EOLIAN static void +_example_colored_color_set(Eo *obj EINA_UNUSED, Example_Colored_Data *pd, + int red, int green, int blue) +{ + pd->red = red; + pd->green = green; + pd->blue = blue; +} + +EOLIAN static void +_example_colored_color_get(Eo *obj EINA_UNUSED, Example_Colored_Data *pd, + int *red, int *green, int *blue) +{ + if (red) + *red = pd->red; + if (green) + *green = pd->green; + if (blue) + *blue = pd->blue; +} + +#include "example_colored.eo.c" +``` + +## Step Ten: Using the Mixin ## + +Finally, modify ``eo_multiinherit_main.c`` to make use of this new functionality. There's little to change. + +Add a new configuration call to ``example_colored_color_set()`` in the creation of the rectangle: + +```c + 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), + example_colored_color_set(efl_added, 255, 0, 0)); +``` + +Likewise for the creation of the square: +```c + square = efl_add(EXAMPLE_SQUARE_CLASS, NULL, + efl_name_set(efl_added, "Square"), + example_rectangle_width_set(efl_added, 7), + example_colored_color_set(efl_added, 64, 64, 64)); +``` + +No need to modify the circle creation since you didn't include the mixin in ``Example.Circle``. + +To finish, in the ``_shape_print()`` method, print some additional information in case the requested shape includes the ``Example.Colored`` mixin. Right after the previous ``printf()`` call: + +```c + if (efl_isa(shape, EXAMPLE_COLORED_MIXIN)) + { + int red, green, blue; + + example_colored_color_get(shape, &red, &green, &blue); + printf(" Colored %d, %d, %d\n", red, green, blue); + } +``` + +``efl_isa()`` checks if the ``Efl.Object`` passed in the first parameter contains the class specified in the second parameter in its inheritance tree. That is, it returns ``true`` if any of the ancestors of the object are of the given class, or implement the given interface, or include the given mixin. If that is the case, you will print the color information using the ``color`` property. + +If you do not perform the ``efl_isa()`` check, you will be assuming that all shapes contain the ``color`` property, and the program will display an error at runtime. + +Build your program, including the additional ``example_colored.c`` file: + +```bash +gcc eo_multiinherit_main.c example_rectangle.c example_square.c example_shape.c example_circle.c example_colored.c `pkg-config --cflags --libs elementary` +``` + +Upon running it, you should get this on the terminal: + +``` +Shape named Circle has area 78 +Shape named Rectangle has area 50 + Colored 255, 0, 0 +Shape named Square has area 49 + Colored 64, 64, 64 +``` + +As you can see, only the rectangle and the square contain color information, since only the rectangle includes the ``Example.Colored`` mixin (and the square inherited it). + +## Summary ## + +After this tutorial, you have learned: + +* That **multiple inheritance** is possible with Eolian, with some restrictions. + +* That only **one class** can be inherited from, but multiple **interfaces** can be **implemented** and multiple **mixins** can be **included**. + +* How to create **interfaces** and implement them in other classes. + +* How to create **mixins** and include them in other classes. + +* How to find if an object has a given class in its inheritance tree using ``efl_isa()``. + +This tutorial concludes the series about Eolian. + +## Further Reading ## + +[Class Inheritance with Eolian](eo-inherit.md) +: Introduces class inheritance with Eolian. \ No newline at end of file --
