0:04
In this episode, we will see
how polymorphism
complements abstraction very well and how it allows us
to better define classes on an abstract level.
But first, let's go over a few reminders on polymorphism.
"Subtype polymorphism", to be precise, [TN: sometimes a.k.a. "inclusion polymorphism" or "dynamic polymorphism"]
refers to the fact that instances of a subclass,
which can substitute for instances of the superclass,
keep their own properties
instead of being considered as having the properties of the superclass
especially with regards to their behavior,
that is, the methods to be invoked
will be determined at runtime
depending on the actual nature of the instances involved.
The definition may seem complicated
but the principle is actually quite simple.
For example, if I have a general class called "Personnage" (TN: means "Character")
with a subclass "Voleur" (TN: means "Thief"): a thief is indeed a character.
We also have "Magicien", "Guerrier", (TN: "Magician", "Warrior"), etc.
Magicians, warriors and thieves,
even if they are all characters,
will be able to behave as different instances,
for example when they meet.
For example, if a thief meets another character
he might steal from him;
if a warrior meets another character,
he might hit him, etc.
So for example
if I have a character "p1" who meets a character "p2",
the "rencontrer" method here (TN: "rencontrer" means "meet")
will adapt to the real nature of the instance "p1"
and will thus meet "p2" as a thief
if "p1" is a "Thief", as a magician if "p1" is a "Magician",
or as a warrior if "p1" is a "Warrior".
And remember that to have polymorphism
you need inheritance to have this general class
with regards to which the methods will adapt
depending on the specific subclasses. Inheritance thus,
but also dynamic binding.
Remember, too, that dynamic binding
consists in calling the right method
depending on the instance. So to go back to an example
from a previous episode,
imagine that we organize a meet-up
between a warrior and a thief.
This method here organizes a meeting
between two characters taken as arguments
and displays a message
and calls the "rencontrer" method
of character 'a', which is the first argument received here,
with character 'b', received as the second argument here.
And dynamic binding will ensure that when
we meet a warrior 'g' as the parameter 'a',
it is the "rencontrer" method of the warrior ("Guerrier")
which will, dynamically, during the execution of this piece of code,
check that 'a' is a warrior
to call the "rencontrer" method of the "Guerrier" class
and not the "rencontrer" method of the "Personnage" class, despite the fact
that 'a' is described as a "Personnage" here,
If we called the "rencontrer" method of the "Personnage" class
which would be a static binding
depending only on what is written statically,
whereas what happens in Java is dynamic binding
where we will call the "rencontrer" method
of the instance which is passed.
We look at what the instance is: it is a "Guerrier" (warrior),
so here we will call the "rencontrer" method of the "Guerrier" class.
3:12
That's it for reminders.
Let's now see how these ingredients,
how polymorphism, will allow us to better specify
our programs at a more abstract level
by introducing this notion of abstract classes
and abstract methods.
Let's start by presenting the problem that these abstract
methods address.
At the highest level of a hierarchy,
we don't necessarily know how to define a method
which we know will exist for all the subclasses
For example, if we imagine a very general class
representing nondescript, closed geometric shapes,
Then, in this class
it will be quite difficult to define
how to calculate its surface area.
Calculating the surface area of a nondescript geometric figure
is certainly something that is hard to define,
we wouldn't really know how to do it.
However, we know that for all closed figures,
for example circles, which are closed figures,
we know that for each specific closed figure
we should be able to defined a "surface" method.
So, we imagine that in all of these closed geometric figures,
we will have a "surface" method,
even if we don't know how to define it at the most abstract level.
And yet, to push the problem a little further yet,
we can even imagine that this "surface" method would
be used at the highest level,
for example to calculate the volume occupied
by a surface over a given height.
This volume
would depend on the height and on the geometric figure
and would be defined as the product of the height
multiplied by the surface of the close figure.
So we could even call this method
without knowing how we would define it
at the highest level. It would be defined
for all the actual cases of closed figures.
The correct way of doing this,
of coding a method which we know must exist
but which we do not necessarily know how to define
at the superclass level,
is to introduce the method
as an "abstract method".
Let's illustrate once more this concept of "abstract methods"
using another example; so, let's go back to
character games, where we have
warriors, who are characters [inheritance],
magicians, who also are characters [inheritance],
thieves, etc.
The class "Jeu" (TN: means "game") would of course contain several characters,
here we decided to put them in a dynamic array of characters.
And imagine that in the game, we wish
to display the whole set of characters.
So of course, we would write a loop
iterating over the set of characters,
this "persos" array here.
And for each of these characters,
we would display this very character.
The problem is, how do we display a generic character?
We don't know how to display a generic character.
We certainly know how to display a warrior,
we certainly know how to display a magician
or a thief.
So each of the specialized subclasses knows how it should be displayed
but at the general level of a character,
we don't necessarily know how to display it.
However, we know that we need to. Here, at the very general level of the game,
we know we will need to display characters.
So how do we do this?
Of course, if we do nothing at the level of the "Personnage" class,
if we don't define the "afficher" method (TN: means "display") in "Personnage"
then the code "afficher" in "Jeu" will not compile
since we are calling the "afficher" method of a "Personnage"
and the compiler would not know what to do
Thus, it would throw an error.
So, in order to be able to write this at the global level of the game,
we must define an "afficher" method in each "Personnage" class.
Moreover, we would like to impose to each of the subclasses,
to warriors and magicians, to display themselves
with their own "afficher" method.
We would like them to have a specific method
and we would like for this specific method to be called
when we make a general call here at the game level.
Basically, we want to force the subclasses to have a specific method
and we want this method to be polymorphic.
But how do we do this if we don't know,
suppose that we don't know how to display a generic character?
Moreover, how do we impose
the fact that this "afficher" method must be redefined?
How do we force its re-definition in the subclasses?
First solution for the first problem:
how to define the display for a generic character.
One way would be to have some arbitrarily-defined method,
for example, suppose that here, arbitrarily,
we define that the display for a generic character
is no display at all.
This solution is a really, really bad idea
because first, it is a bad model of reality,
characters should not be displayed as nothing
so it doesn't correspond to anything,
the display is incorrect. And on top of that,
the display would be incorrect if a subclass
were to forget to redefine the method,
so we would have some characters that would not be displayed,
phantom characters, and this would be rather inconvenient for the game.
What's more, this solution does not address the second problem,
it does not force the subclasses to redefine
their own "afficher" methods.
The only good solution
is thus to signal that the "afficher" method must exist
and must be redefined in each of the subclasses
This is known as an "abstract method".
So you have a second example,
which, I hope, allows you to understand
the point of having abstract methods
such as this one, defined at the level of superclasses, of abstract classes.
Now, let's see how this is actually written in Java.
To have an abstract method in Java,
we simply prepend the reserved keyword "abstract" to its header
and we simply end this header
with a semicolon ( ; ) without writing a body,
without giving it any definition
because abstract methods don't have a definition
in the class in which they were introduced.
They simply serve to impose to the subclasses
which we do not want to define as being abstract
-- we will come back to this in a moment --
that they should redefine this inherited abstract method.
And these methods must be contained within an abstract class,
and again we will come back to this in a moment,
and an abstract class is a class
which also has the "abstract" keyword in its header.
So for example here,
we want the "Personnage" class to be an abstract class
so we will add the "abstract" keyword to the header like so,
at the beginning of the class header
and it will contain an abstract method,
such as the method "abstract afficher" here.
If we go back to our example with the closed figures,
remember that the idea was to have closed figures
to define a "surface" method.
But we do not know how to define this "surface" method
in a general way at the level of the "FigureFermee" class
and so this "surface" method becomes an abstract method
and the "FigureFermee" class becomes an abstract class
and we imagine that we will have concrete subclasses
defining closed figures such as a circle, for example.
Since the "FigureFermee" is an abstract class,
we add the "abstract" keyword here.
The class contains two methods
we added another abstract one here.
So we have the "surface" method we were talking about earlier on in our example
to which we added the "abstract" keyword,
and we could also imagine
that we will have a "perimetre" method
which would return the perimeter of a closed figure
and which we do not know how to define in a general way
at the level of the closed figures,
so it is also an abstract method.
Remember that you can use, in an abstract class,
an abstract method within a non-abstract method.
So for example here,
calculating the volume generated by the surface of the closed figure
over a certain height passed as a parameter here
would consist in calculating the product of the height and the value
returned by the abstract method "surface". This is quite possible.
So an abstract class,
qualified by the keyword "abstract" at the beginning of its header:
"abstract class something",
is a class that cannot be instantiated
and that contains at least one abstract method.
This is why it is known as an abstract class,
because we cannot create an instance of it
So for example, if I wanted to write "FigureFermee"
to declare an instance of a closed figure,
e.g " fig = new ... "
followed by a FigureFermee constructor,
I would not be able to do this, the compiler would prevent me from doing so
since "FigureFermee" is an abstract class,
meaning that I cannot create an instance of it.
And subclasses of an abstract class
stay abstract as long as they haven't redefined
all of its abstract methods.
That is, they remain abstract
as long as at least one inherited abstract class
hasn't been defined.
Let's take an example,
the example of the game with its characters,
where "Personnage" is an abstract class
which contains an abstract method "afficher".
"Guerrier" is a subclass of "Personnage".
Imagine that "Guerrier" forgets
to redefine the abstract method "afficher" from "Personnage".
If I do the following:
so, if I create a game and I try to add
a new warrior to the game,
if "Guerrier" has forgotten to redefine the abstract method from "Personnage"
then "Guerrier" is also an abstract class
and I will not be able to create an instance of it,
of this abstract class "Personnage".
If I try to do this, I will get the message:
"Guerrier is abstract; cannot be instantiated".
I cannot create an instance of the abstract class "Guerrier"
which is still abstract since it did not
redefine the "afficher" method.
If I take a second example on the closed figures,
let's imagine that we still have this abstract class "FigureFermee"
with its abstract methods "surface" and ...
...
"perimetre", which is also abstract,
and suppose that we define a class like so, "Cercle" [TN: means "Circle"],
inheriting from "FigureFermee".
This "Cercle" class
redefines the "surface" method here concretely,
in a non-abstract way,
for example, Pi times the radius squared
and it also redefines the "perimetre" method concretely.
It has thus redefined both abstract methods
inherited from its abstract superclass "FigureFermee"
and so this "Cercle" class is no longer an abstract class,
we are able to create instances of "Cercle".
This is now possible
because the "Cercle" class is no longer abstract.
However, let's imagine a "Polygone" class
also inheriting from the "FigureFermee" class
but which redefines only the perimeter, for example,
supposing that we calculate the perimeter of a polygon
as being the sum of the lengths of its sides.
So we know perfectly well how to define this
but we would not necessarily know how to define in a general way
the surface of a polygon, and so the "Polygon" class
would only redefine the abstract method "perimetre"
but would not redefine the abstract method "surface".
And so to "Polygone" class would remain an abstract class.
I would not be able to create any instance of "Polygone".
This is impossible because
"Polygone" is an abstract class
since it doesn't redefine the abstract method "surface".
By the way, there is something missing here
to make the code for "Polygone" correct --
do you know what it is?