Aller au contenu

Programmation orientée objet

Le paradigme OOP voit le programme comme un ensemble d'objets qui interagissent entre eux.

Classes, objets et héritage

  • Chaque objet est défini par une classe qui elle même hérite d'autres classes ou interfaces.
  • Une classe peut définir des propriétés et des méthodes, qu'on appelle des membres.
    • Propriété : une vue sur une donnée via ses accesseurs en lecture et / ou écriture (qu'on appelle getters et setters respectivement).
    • Méthode : fonction qui est définie au sein de la classe
    • Constructeur : méthode particulière qui sera appelée automatiquement au moment de la création de l'instance
  • En python, le premier argument des méthodes et du constructeur est une référence vers l'objet actuel
    • Le nom de cet argument doit s'appeler self (appelé this dans d'autres langages)
    • On appelle self le contexte de la méthode
  • Une classe enfant peut hériter d'une classe parente:
    • Dans ce cas, la classe enfant contiendra implicitement tous les membres de la classe mère
    • La classe enfant peut définir des membres supplémentaires qui lui seront propres
    • La classe enfant peut redéfinir des membres de classe parent. On appelle cela une surcharge ou override en Anglais.
  • Python est l'un des rares langages (avec le C++) à permettre l'héritage multiple. i.e. une classe peut hériter de plusieurs classes à la fois.
Définition d'une classe
class Rectangle:
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return (self.width + self.height) * 2

    def __str__(self):
        return f"x: {self.x}, ..."


r = Rectangle(10, 1, 100, 5)
print(r)
Héritage
class Person:
    # constructeur
    # self est l'équivalent du this dans les autres langages
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say_name(self):
        print(self.name)

    def __str__(self):
        return f"Person: {self.name} - {self.age}"


class Student(Person):
    def __init__(self, num_student, name, age):
        self.num_student = num_student
        # appel du constrcteur parent
        super().__init__(name, age)

    def __str__(self):
        return f"Student {self.num_student}, Parent __str__:" + super().__str__()


pDico = {"name": "dfsdfs", "age": 230}

p1 = Person("John", 36)

print(p1.name)
print(p1.age)
p1.age = 10
p1.say_name()
print(p1, pDico)

s = Student("2323JFLSJ", "A", 19)
print(s)

Agrégation, composition et association

  • L'agrégation et la composition représentent la relation avoir (l'héritage représente la relation être pour rappel)/
    • L'agrégation est une relation entre deux classes où une classe contient une référence vers une autre classe.
    • La composition est une relation plus forte que l'agrégation. Dans ce cas, la classe enfant ne peut pas exister sans la classe parente.
  • L'association est une relation entre deux classes où une classe utilise l'autre sans en contenir une référence.
class Salary:
    def __init__(self, pay):
        self.pay = pay

    def get_total(self):
        return self.pay * 12


class Car:
    pass


class House:
    pass


class Computer:
    def turn_on(self):
        print("Turning on")


class Employee:
    def __init__(self, pay, bonus, posessions):
        self.bonus = bonus
        # Composition
        self.salary = Salary(pay)
        # Agrégation
        self.posessions = posessions

    def compute_annual_salary(self):
        return self.salary.get_total() + self.bonus

    def work(self, computer):
        """Association entre Employee et Computer"""
        computer.turn_on()


c1 = Car()
c2 = Car()
h = House()
emp = Employee(100, 10, [c1, c2, h])
print(emp.compute_annual_salary())
comp = Computer()
emp.work(comp)

UML

  • UML (Unified Modeling Language) est un langage de modélisation graphique pour la conception de logiciels.
  • Il permet de représenter les classes, les objets, les relations entre les objets, les cas d'utilisation, les séquences, etc.

Voici un exemple de diagramme de classe UML qui illustre l'héritage entre deux classes :

Exemple UML

Deux classes qui héritent d'une classe
class SchoolPerson:
  """Classe de base ou une classe mère pour les classes Student et Teacher"""
  def __init__(self, name, id, address):
    self.name = name
    self.id = id
    self.address = address

  def __str__(self):
    return f"[SchoolPerson: {self.name}, {self.id}, {self.address}]"

class Student(SchoolPerson):
  """Un Student 'est' un SchoolPerson donc Student peut hériter de SchoolPerson"""
  def __init__(self, name, id, address):
    super().__init__(name, id, address)


class Teacher(SchoolPerson):
  """Un Teacher 'est' un SchoolPerson donc Teacher peut hériter de SchoolPerson"""
  def __init__(self, name, id, address, contract_type):
    super().__init__(name, id, address)
    self.contract_type = contract_type

  def __str__(self):
    #return f"[Teacher: {self.name}, {self.id}, {self.address}, {self.contract_type}]"
    return f"[Teacher: {self.contract_type}, ({super().__str__()})]"

s1 = Student("a", "12354S", "Home")
t1 = Teacher("b", "82546T", "Teacher Home", "Occasionnel")
print(s1, t1)

Voici un exemple de diagramme qui illustre l'association et la la composition :

Diagramme UML

Deux classes qui héritent d'une classe
import datetime


class LivingBeing:
    def __init__(self, name, birth_date) -> None:
        self.name = name
        self.birth_date = birth_date


class Brain:
    def __init__(self, volume) -> None:
        self.volume = volume


class Hand:
    def __init__(self, finger_count) -> None:
        self.finger_count = finger_count

    def do_thumbs_up(self):
        print("👍")


class Heart:
    pass


class Human(LivingBeing):
    def __init__(
        self, name, birth_date, social_security_number, height, heart, brain_volume
    ) -> None:
        super().__init__(name, birth_date)
        self.social_security_number = social_security_number
        self.height = height
        # Composition
        self.brain = Brain(brain_volume)
        self.hands = [Hand(3), Hand(12)]
        # Agrégation
        self.heart = heart
        self.hp = 10

    def walk(self, distance):
        print(f"🚶 during {distance} KM")

    def sleep(self, duration):
        print(f"It's time to 😴 for {duration} minutes")

    def talk(self, sentence):
        print(f"🗣️: {sentence}")

    def eat(self, fruit):
        print(f"miam la pomme {fruit}")
        fruit.weight = 1
        self.hp += 10

    def heal(self, human):
        print(f"Soin appliqué à {human}")
        human.hp += 3

    def __str__(self) -> str:
        return f"Human: hp -> {self.hp}"


class Fruit:
    def __init__(self) -> None:
        self.weight = 10

    def __str__(self) -> str:
        return f"Fruit: weight -> {self.weight}"


heart = Heart()
human1 = Human("Rémie", datetime.datetime(2003, 8, 15), 1_987_687_686, 175, heart, 2)

print("brain volume", human1.brain.volume, "M3")
human1.sleep(10)
human1.walk(2000)
human1.talk("hahaha")

a1 = Fruit()
a2 = Fruit()

human1.eat(a1)
human1.eat(a2)

h2 = Human("Dupont", datetime.datetime(2000, 8, 15), 100, 175, Heart(), 2)
human1.heal(h2)

print(human1, h2)

Propriétés, classes et méthodes statiques

  • Propriété d'instance : chaque instance a ses propres propriétés d'instances
  • Propriété statique : elle sera partagée entre toutes les instances (comme une variable globale pour la classe)
  • Méthode d'instance : sera exécutée dans le contexte de l'instance qui l'a appelée (accessible via self)
  • Méthode statique : méthode qui a comme contexte que les propriétés et méthodes statiques de sa classe
  • Classe statique : une classe qui ne peut pas être instanciée et ne contiendra donc que des propriétés et méthodes statiques
class Calculator:
  PI = 3.14

  @staticmethod
  def add(x, y):
    return x + y

  @staticmethod
  def multiply(x, y):
    return x * y

  @staticmethod
  def compute_circle_area(r):
    return r * r * Calculator.PI


print(Calculator.add(5, -8), Calculator.multiply(73, 0.8))
print(Calculator.PI, Calculator.compute_circle_area(8))

Propriétés, classes et méthodes abstraites

  • Méthode abstraite: méthode qui n'a pas d'implémentation
  • Propriété abstraite: propriété dont les accesseurs ne sont pas définis
  • Classe abstraite: une classe qui a au moins une propriété ou méthode abstraite
  • Les membres abstraits sont destinés à être définis par une sous-classe non abstraite.
from abc import ABC, abstractmethod
import random


class SchoolPerson(ABC):
    def __init__(self, name, id, address):
        self.name = name
        self.id = id
        self.address = address

    def __str__(self):
        return f"[SchoolPerson: {self.name}, {self.id}, {self.address}]"

    @abstractmethod
    def attend_class(self):
        """@abstractmethod rend l'instanciationd de cette classe impossible et oblige les sous-classes à implémenter la méthode"""
        pass


class Student(SchoolPerson):
    """Un Student 'est' un SchoolPerson donc Student peut hériter de SchoolPerson"""

    def __init__(self, name, id, address):
        super().__init__(name, id, address)

    def attend_class(self):
        print("I learn")


class Teacher(SchoolPerson):
    """Un Teacher 'est' un SchoolPerson donc Teacher peut hériter de SchoolPerson"""

    def __init__(self, name, id, address, contract_type):
        super().__init__(name, id, address)
        self.contract_type = contract_type

    def __str__(self):
        # return f"[Teacher: {self.name}, {self.id}, {self.address}, {self.contract_type}]"
        return f"[Teacher: {self.contract_type}, ({super().__str__()})]"

    def attend_class(self):
        print("I teach")


s1 = Student("a", "12354S", "Home")
t1 = Teacher("b", "82546T", "Teacher Home", "Occasionnel")
print(s1, t1)

s1.attend_class()
t1.attend_class()