Object Oriented Programming - 4 Principles
Intro
Une fois qu'on a bien compris comment écrire des classes, il faut passer en revue quelques règles de code créées pour tirer le meilleur parti de tes classes. Si tu n'es pas sûre de savoir écrire une classe, reviens en arrière et révise ces quêtes :
On va continuer avec l'exemple du chat Tamagochi commencé dans la quête précédente. Si tu n'es pas sûre de quelle partie du code on va réutiliser, tu la trouveras ici.
Content
Après avoir terminé cette quête, tu seras capable de : ✅ écrire des classes de façon plus efficace, ✅ produire du code qui sera mieux apprécié par l'utilisateur, ✅ répondre aux recruteurs techniques quand ils demandent « Quels sont les 4 principes de la programmation orientée objet ? »
Les 4 principes
Les 4 principes vont t'aider à écrire du code plus propre, ce qui est top à la fois pour ton toi futur et pour les autres, utilisateurs comme développeurs. Les principes sont :
- L'abstraction
- L'héritage (inheritance)
- L'encapsulation
- Le polymorphisme (polymorphism)
Avant de commencer, tu trouveras ici un bref aperçu.
1. L'abstraction
Le principe d'abstraction (abstraction) dicte que toute l'implémentation est cachée à l'intérieur du code et n'apparaît pas à l'extérieur.
Un bon exemple serait une application de téléphone : quand tu sélectionnes une app et que tu cliques sur l'icône, ce que tu vois c'est le résultat final, pas ce qui se passe du côté du traitement. Les apps sont un exemple d'abstraction. Le code fait quelque chose, mais l'utilisateur ne le voit pas.
Revenons à notre exemple du Tamagochi, essaie de jouer avec la méthode sleep modifiée de cette façon :
def sleeps(self):
# First, let's check if the cat is alive: the cat is alive if the cat was alive before and it's also healthy
print('Checking if the cat is alive ...')
self.is_alive = (self.is_alive and (self.health >= 0))
if self.is_alive:
# The cats sleeps for 8 hours straight each time, gains one health point and one hunger point each time and the total amount of playfulness points
print('The cat is alive!')
print("Changing the sleepiness' value ...")
self.sleepiness = 0
print("Changing the playfulness' value ...")
self.playfulness = 10
print("Changing the health's value ...")
self.health += 1 if self.health < 10 else 0
print("Changing the hunger's value ...")
self.hunger += 1 if self.hunger < 10 else 0
print(f"The cat {self.name} has slept!")
else:
print(f"Condolences, {self.name} is dead! If you want to play again, create a new Cat object.")
C'est pénible, non ? Alors garde bien en tête le principe d'abstraction !
Afficher des variables avec print est une méthode rapide de debug (debugging : traque des erreurs dans le code) qui marche très bien pendant que tu développes. Ce n'est pas que tu ne dois jamais utiliser print, mais tu ne dois pas l'utiliser quand ton code passe en production (production : la version finale, en ligne, utilisée par les vrais utilisateurs).
2. L'héritage
L'héritage (inheritance) est le principe qui permet à une classe d'être modelée d'après une autre. La classe initiale est appelée superclass (superclasse) ou class parent (classe parente).
Avant de passer à notre exemple, jette un œil ici.
Si tu préfères une vidéo, regarde celle-ci.
Imagine qu'il y ait la possibilité de sélectionner dans notre jeu Tamagochi un type de chat précis : le House Cat (chat de maison). Le House Cat a toutes les options du Cat plus l'action supplémentaire de prendre un bain : pendant cette action il perd beaucoup d'énergie parce qu'il n'arrête pas d'essayer de sortir de la baignoire, mais il gagne en santé parce qu'il est plus propre.
On aurait donc besoin de créer une classe qui a quasiment les mêmes attributs et méthodes que la classe précédente, sauf l'action de prendre un bain. Ça semble une perte de temps de retaper (ou copier-coller) tout le code précédent, non ? Imagine chercher quoi que ce soit dans le script : un cauchemar ! On peut donc utiliser la classe précédente et dire au code que cette nouvelle classe a tout ce qu'avait la classe précédente, en ajoutant la nouvelle méthode. La classe initiale, celle sans la nouvelle méthode, c'est la superclass (superclasse)
La structure pour dire au code que la nouvelle classe garde tous les attributs et méthodes de l'autre (c'est ça, l'héritage) est la suivante :
class new_class(parent_class):
# rest of the code
Pour ajouter (et exécuter) le code selon la nouvelle structure, il faudrait ajouter :
class House_Cat(Cat):
# Now lets add the additional method
def bath_time(self, minutes_of_washing = 10):
# First, let's check if the cat is alive: the cat is alive if the cat was alive before and it's also healthy
self.is_alive = (self.is_alive and (self.health >= 0))
if self.is_alive:
# The cat gets sleepier and healthier
self.sleepiness = 8 if self.sleepiness < 8 else 10
self.health +=1
else:
print(f"Condolences, {self.name} is dead! If you want to play again, create a new Cat object.")
3. L'encapsulation
L'encapsulation (encapsulation) est le principe qui dicte que nous, les développeurs (surtout les gens de la Data), on doit éviter que l'utilisateur puisse accéder directement aux attributs, et quand c'est possible l'utilisateur devrait appeler une méthode pour y accéder.
Dans notre exemple Tamagochi, on pourrait afficher l'état de notre objet, mais pas le modifier. Les valeurs ne pourraient être changées qu'en utilisant une méthode. Ne pas autoriser l'utilisateur à changer les valeurs des attributs est une solution plus sûre contre les erreurs qui mènent à une perte d'information. Imagine ce qui se passerait si on passait par erreur l'attribut is_alive à False.
Les Data Analysts (analystes de données) devraient particulièrement garder ça en tête, car l'information contenue dans la Data est ce qu'ils doivent préserver pour pouvoir la traiter. Si on perd (ou change) la Data, qu'est-ce qu'on va bien pouvoir traiter ?
4. Le polymorphisme
Le polymorphisme (polymorphism) est le principe selon lequel quelque chose peut se produire sous plusieurs formes. Pour notre exemple Tamagochi, imagine maintenant qu'il y ait une option pour choisir quel type de Cat on veut. Il y a deux possibilités :
- Le House Cat (chat de maison)
- Le Stray Cat (chat errant)
La différence se situe entre les résultats du moment du bain. Le Stray Cat ne se baigne pas beaucoup, du coup il devient propre en plus de temps. Il lui faut juste plus de temps pour enlever toute la poussière de son pelage. Le House Cat est plus propre, donc il met moins de temps à devenir propre. Pour le reste, il n'y a aucune différence entre les deux chats.
Dans cette situation, on a plein de choses en commun entre les deux options (le Stray Cat et le House Cat), comme tous les attributs et la plupart des méthodes, et une seule différence, la méthode du bain. Si on écrivait deux classes différentes, il y aurait beaucoup de répétition dans le code. En plus, on a déjà créé la classe House_Cat pour ajouter la méthode du bain. Ce serait mieux de créer une classe pour chaque type de chat et d'écrire deux méthodes différentes. Comme c'est la même méthode (avec des résultats différents), on pourrait tout à fait initialiser une méthode dans la superclasse Cat et la laisser vide, puis préciser ce qu'elle fait dans chaque classe. C'est l'application du principe de polymorphisme.
Maintenant, voyons comment le code change.
D'abord, on doit changer la classe Cat, on doit introduire une méthode vide, le bain.
class Cat():
# This is the constructor we previously saw
def __init__(self, name, sleepy, hungry, cold, playfulness, health):
## Let's add all the cat can be
self.name = name # example of variable filled during initialization
self.sleepiness = sleepy # This could be a boolean or a value from 0 to 9
self.hunger = hungry # This could be a boolean or a value from 0 to 9
self.cold = cold
self.playfulness = playfulness # Could also be thought as energy
self.health = health
## Now let's add all the actions the cat can take
def sleeps(self):
# The cats sleeps for 8 hours straight each time, gains one health point and one hunger point each time and the total amount of playfulness points
self.sleepiness = 0
self.playfulness = 10
self.health += 1 if self.health < 10 else 0
self.hunger += 1 if self.hunger < 10 else 0
print(f"The cat {self.name} has slept!")
def bath_time(self, mins_of_washing = 10): # We declare the function inside the superclass but leave it empty because we are going to specify it in the House_Cat class
pass
def plays_inside(self, mins_of_play = 30):
# The cat gets spleepier with one hour of play and loses playfulness each hour
self.playfulness = (self.playfulness - mins_of_play /6) if mins_of_play < 60 else 0 # normal use of ternary function
self.sleepiness = (self.sleepiness + mins_of_play /6) if mins_of_play < 60 else 10
print(f"The cat {self.name} has played for {mins_of_play} minutes inside the house.")
def eats(self, spoons_of_tuna = 2):
# The cat loses the hunger after it eats 5 spoons of tuna, gets one more point more playful after 4 spoons of tuna
self.hunger = (self.hunger - spoons_of_tuna * 2) if spoons_of_tuna < 5 else 0
self.playfulness = (self.playfulness + int(spoons_of_tuna / 4)) if self.playfulness < 10 else 10
print(f"The cat {self.name} has eaten {spoons_of_tuna} spoons of tuna. Yum!")
Ensuite, on doit créer les classes des chats :
class Stray_Cat(Cat): # child of the Class Cat
# Now lets add the additional method
def bath_time(self, mins_of_washing = 10):
# First, let's check if the cat is alive: the cat is alive if the cat was alive before and it's also healthy
self.is_alive = (self.is_alive and (self.health >= 0))
if self.is_alive:
# The cat gets sleepier and healthier
self.sleepiness = 8 if self.sleepiness < 8 else 10
self.health +=1
else:
print(f"Condolences, {self.name} is dead! If you want to play again, create a new Cat object.")
class House_Cat(Cat): # child of the Class Cat
# Now lets add the additional method
def bath_time(self, mins_of_washing = 10):
# First, let's check if the cat is alive: the cat is alive if the cat was alive before and it's also healthy
self.is_alive = (self.is_alive and (self.health >= 0))
if self.is_alive:
# The cat gets sleepier and healthier
self.sleepiness = 8 if self.sleepiness < 8 else 10
self.health += 3 # In this case the cat becomes more healthier
else:
print(f"Condolences, {self.name} is dead! If you want to play again, create a new Cat object.")
Maintenant, il ne reste plus qu'à exécuter le code suivant :
captain_meow = Stray_Cat('Captain Meow', 0, 0, 0, 10, 10)
print(captain_meow.health )
captain_meow.bath_time(30)
print(captain_meow.health )
captain_meow_h = House_Cat('Captain Meow Housecat', 0, 0, 0, 10, 10)
print(captain_meow_h.health )
captain_meow_h.bath_time(30)
print(captain_meow_h.health )
Le polymorphisme est le principe grâce auquel on peut avoir la même fonction qui fait des choses différentes selon la classe enfant. Pratique, non ?
💪 Défi
Utilise le code que tu as fait pour le défi de la quête précédente.
- Ajoute une classe enfant pour un type de chien (par exemple un Chihuahua ou un Terrier). Invente une méthode supplémentaire pour la classe enfant.
- Maintenant, ajoute une autre classe enfant avec le même nom de méthode mais un résultat différent. Pense à ajouter la méthode vide dans la classe parente.
- Ajoute quelques lignes de code de plus au code de test pour vérifier si les nouvelles classes et méthodes fonctionnent correctement.
Critères de validation
- Le fichier partagé est accessible à toute personne qui a le lien
- Toutes les missions ont été réalisées
Intro
Once we have grasped how to write classes, we need to go over some code rules created to make the most out of our classes. If you are ensure about how to write a class, please go back and review these quests:
We will continue with the example of the Tamagochi cat we started in the previous quest. If you are not sure about which part of the code we will continue using, please find it here.
Content
After completed this quest, you will be able to: ✅ write classes in a more efficient way, ✅ produce code that will be better appreciated by the user, ✅ answer the tech interviewers when they ask "What are the 4 Principles of Object Oriented Programming?"
The 4 Principles
The 4 Principles will help you to write cleaner code, which is great both for your-future-self and for others, users and coders alike. The Principles are:
- Abstraction
- Inheritance
- Encapsulation
- Polymorphism
Before we start, find here a brief overview.
1. Abstraction
The Abstraction Principle dictates that all implementation is hidden inside the code and it doesn't show outside.
A good example would be a phone app: when you select an App and click on the icon, what you can see is the end result, not what's happening on the processing side. Apps are an example of abstraction. The code does something, but the user doesn't see it.
Back to our Tamagochi example, try to play with the method sleep changed this way:
def sleeps(self):
# First, let's check if the cat is alive: the cat is alive if the cat was alive before and it's also healthy
print('Checking if the cat is alive ...')
self.is_alive = (self.is_alive and (self.health >= 0))
if self.is_alive:
# The cats sleeps for 8 hours straight each time, gains one health point and one hunger point each time and the total amount of playfulness points
print('The cat is alive!')
print("Changing the sleepiness' value ...")
self.sleepiness = 0
print("Changing the playfulness' value ...")
self.playfulness = 10
print("Changing the health's value ...")
self.health += 1 if self.health < 10 else 0
print("Changing the hunger's value ...")
self.hunger += 1 if self.hunger < 10 else 0
print(f"The cat {self.name} has slept!")
else:
print(f"Condolences, {self.name} is dead! If you want to play again, create a new Cat object.")
It's bothersome, isn't it? So please be mindful of the Abstraction Principle!
Printing variables is a quick debugging method that works very well while you are developing. It's not that you never have to use print, you don't have to use when your code goes into production.
2. Inheritance
Inheritance is the Principle that allows a class to be modeled after another. The initial class is called superclass or class parent.
Before we move to our example, have a look here.
If you prefer a video watch this.
Imagine that there is a possibility to select in our Tamagochi game a specific kind of cat: the House Cat. The House Cat has all the options of the Cat and the additional action of bathing: during this action it loses a lot of energies because it keeps trying to get out of the tab, but he earns in health because it is cleaner.
So we might need to create a class that has mostly the same attributes and methods of the previous class, except the bathing action. It seems a waste of our time to type (or copy paste) all the previous code, doesn't it? Imagine looking at the script to find anything: a nightmare! So we can use the previous Class and tell the code that this new Class has everything the previous Class had, adding the new method. The initial Class, the one without the new method, is the superclass
The structure to tell the code the new class keeps all the attributes and methods of the other one (which is the inheritance) is the following:
class new_class(parent_class):
# rest of the code
To add (and run) the code following the new structure we would need to add:
class House_Cat(Cat):
# Now lets add the additional method
def bath_time(self, minutes_of_washing = 10):
# First, let's check if the cat is alive: the cat is alive if the cat was alive before and it's also healthy
self.is_alive = (self.is_alive and (self.health >= 0))
if self.is_alive:
# The cat gets sleepier and healthier
self.sleepiness = 8 if self.sleepiness < 8 else 10
self.health +=1
else:
print(f"Condolences, {self.name} is dead! If you want to play again, create a new Cat object.")
3. Encapsulation
Encapsulation is the Principle that dictates that us developers (especially Data people) should avoid the possibility of the user getting direct access to the attributes and when possible user should call a method to access them.
In our Tamagochi example we could display the state of our object, but not changed. The values could only be changed by using a method. Not allowing the user to change the attributes' values is a safer solution against mistakes that lead to loss of information. Imagine what would happen if we mistakenly turn the is_alive attribute to False.
Data Analysts should especially keep this in mind, as the information inside the Data is what they need to preserve in order to process it. If we lose (or change) the Data, what are we going to process?
4. Polymorphism
Polymorphism is the Principle for which something can happen in multiple forms. For our Tamagochi example, imagine now that there is an option to choose which kind of Cat we want. There are two possibilities:
- The House Cat
- The Stray Cat
The difference is between the results of the bathing-time. The Stray Cat does not bathe much, therefore it gets clean in a bigger amount of time. It just takes more to remove all the dust from its coat. The House Cat is cleaner, so it takes less time to get clean. For the rest, there is no difference between the two cats.
In this situation we have a lot of stuff in common between the two options (the Stray Cat and the House Cat), like all the attributes and most of the methods, and one difference, the bathing method. If we were to write two different classes there would be a lot of repetition in the code. On top of it, we already create the House_Cat class to add the bath method. It would be better to create a class for each type of cat and write two different methods. Since it's the same method (with different outcomes), we could definitely initialize a method in the superclass Cat and keep it empty, then specify what it does in each class. This is the application of the Polymorphism Principle.
Now let's see how the code changes.
Firstly, we need to change the Cat Class, we need to introduce an empty method, the bath.
class Cat():
# This is the constructor we previously saw
def __init__(self, name, sleepy, hungry, cold, playfulness, health):
## Let's add all the cat can be
self.name = name # example of variable filled during initialization
self.sleepiness = sleepy # This could be a boolean or a value from 0 to 9
self.hunger = hungry # This could be a boolean or a value from 0 to 9
self.cold = cold
self.playfulness = playfulness # Could also be thought as energy
self.health = health
## Now let's add all the actions the cat can take
def sleeps(self):
# The cats sleeps for 8 hours straight each time, gains one health point and one hunger point each time and the total amount of playfulness points
self.sleepiness = 0
self.playfulness = 10
self.health += 1 if self.health < 10 else 0
self.hunger += 1 if self.hunger < 10 else 0
print(f"The cat {self.name} has slept!")
def bath_time(self, mins_of_washing = 10): # We declare the function inside the superclass but leave it empty because we are going to specify it in the House_Cat class
pass
def plays_inside(self, mins_of_play = 30):
# The cat gets spleepier with one hour of play and loses playfulness each hour
self.playfulness = (self.playfulness - mins_of_play /6) if mins_of_play < 60 else 0 # normal use of ternary function
self.sleepiness = (self.sleepiness + mins_of_play /6) if mins_of_play < 60 else 10
print(f"The cat {self.name} has played for {mins_of_play} minutes inside the house.")
def eats(self, spoons_of_tuna = 2):
# The cat loses the hunger after it eats 5 spoons of tuna, gets one more point more playful after 4 spoons of tuna
self.hunger = (self.hunger - spoons_of_tuna * 2) if spoons_of_tuna < 5 else 0
self.playfulness = (self.playfulness + int(spoons_of_tuna / 4)) if self.playfulness < 10 else 10
print(f"The cat {self.name} has eaten {spoons_of_tuna} spoons of tuna. Yum!")
Then we need to create the Classes of the Cats:
class Stray_Cat(Cat): # child of the Class Cat
# Now lets add the additional method
def bath_time(self, mins_of_washing = 10):
# First, let's check if the cat is alive: the cat is alive if the cat was alive before and it's also healthy
self.is_alive = (self.is_alive and (self.health >= 0))
if self.is_alive:
# The cat gets sleepier and healthier
self.sleepiness = 8 if self.sleepiness < 8 else 10
self.health +=1
else:
print(f"Condolences, {self.name} is dead! If you want to play again, create a new Cat object.")
class House_Cat(Cat): # child of the Class Cat
# Now lets add the additional method
def bath_time(self, mins_of_washing = 10):
# First, let's check if the cat is alive: the cat is alive if the cat was alive before and it's also healthy
self.is_alive = (self.is_alive and (self.health >= 0))
if self.is_alive:
# The cat gets sleepier and healthier
self.sleepiness = 8 if self.sleepiness < 8 else 10
self.health += 3 # In this case the cat becomes more healthier
else:
print(f"Condolences, {self.name} is dead! If you want to play again, create a new Cat object.")
Now all that's left is to run the following code:
captain_meow = Stray_Cat('Captain Meow', 0, 0, 0, 10, 10)
print(captain_meow.health )
captain_meow.bath_time(30)
print(captain_meow.health )
captain_meow_h = House_Cat('Captain Meow Housecat', 0, 0, 0, 10, 10)
print(captain_meow_h.health )
captain_meow_h.bath_time(30)
print(captain_meow_h.health )
Polymorphism is the Principle for which we can have the same function doing different things depending on the child Class. Handy, ah?
💪 Challenge
Use the code you made for the previous quest's challenge.
- Add a child Class for a type of dog (for example a Chihuahua or a Terrier). Invent an additional method for the child class.
- Now add another child class with the same name of the method but with a different outcome. Remember to add the empty method in the parent class.
- Add some more lines of code to the Test Code to check if the new classes and methods work properly.
Validation criteria
- The linked file is available to anyone who has the link
- All the missions have been completed