Skip to content

Les diagrammes de classe

La phase de conception permet de se préparer au mieux au développement. Parmi les tâches de cette phase est la définition des différents algorithmes, l'identification des données nécessaires au bon fonctionnement du projet, etc. Dans ce chapitre, nous allons étudier un aspect important de la conception en POO: les diagrammes de classe.

Un diagramme de classe est tout dessin qui permet de représenter: les classes, les interfaces ainsi que leurs relations (héritage, implémentation, composition, agrégation). Il existe plusieurs façons de dessiner un diagramme, un des standard les plus connus sont les diagrammes de classe UML.

Rappel des relations

  • Héritage: relation "est un"
  • Implémentation: nom de l'héritage pour les interfaces
  • Composition: A a une ou plusieurs instances de B et B est détruit si A est détruit (en d'autres termes B dépend A). Exemple: les chambres d'une maison.
  • Agrégation: A a une ou plusieurs instances de B et B n'est pas forcément détruit si A est détruit (en d'autres termes B ne dépend pas de A). Exemple: le moteur d'une voiture.
  • Association:
    • Association unidirectionnelle: A peut appeler les méthodes et propriétés de B mais pas l'inverse
    • Association bidirectionnelle: A peut appeler les méthodes et propriétés de B et inversement
    • Association avec soi-même: A peut appeler les méthodes et propriétés de d'une autre instance de A

Les diagrammes de classe UML

UML est une norme qui définit comment représenter différents types de diagrammes. Parmi ces différents types de diagrammes, on trouve les diagrammes de classe. Cet aide-mémoire permet de voir les grandes lignes.

Exemple 1

Exemple UML1

Exemple 2

Exemple UML2

Ce diagramme permet d'obtenir le code suivant:

Code correspondant au diagramme de classe diagramme UML
    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)

Exercices

Pour dessiner les diagrammes UML, vous pouvez-utiliser diagrams.net ou cette extension VSCode.

Exercice 1

Dessiner le diagramme de classes UML pour chacun groupe de classes (1 diagramme par cas):

  • Meuble, Armoire, Table, Jardin, Chambre, Maison
  • Pâtisserie, Mille-Feuilles, Pain, Farine, Sel, Sucre
  • Véhicule, Voiture, Camion, Vélo, Roue, Moteur, Volant
  • Voiture, Berline, SUV, Roue, Moteur, Personne, Commercial, Mécanicien, GarageAutomobile

Coder en python un des cas.

Solution du dernier cas

Exemple UML

Solution en Python
class Roue:
    pass


class Moteur:
    pass


class Voiture:
    def __init__(self, roues, moteur) -> None:
        if len(roues) < 3 or len(roues) > 5:
            raise Exception(
                "Pass assez de roues" if len(roues) < 3 else "Trop de roues"
            )
        self.roues = roues
        self.moteur = moteur


class Suv(Voiture):
    pass


class Berline(Voiture):
    pass


class Personne:
    pass


class Mécanicien(Personne):
    pass


class Commercial(Personne):
    pass


class GarageAutomobile:
    def __init__(self, voitures, personnes) -> None:
        if len(personnes) == 0:
            raise Exception("Il faut au moins une personne")
        self.personnes = personnes
        self.voitures = voitures


personnes = [Mécanicien(), Commercial(), Mécanicien()]
roues1 = [Roue()] * 5
roues2 = [Roue()] * 3
moteur = Moteur()
voitures = [Berline(roues1, Moteur()), Suv(roues2, moteur)]

try:
    Berline([Roue()], Moteur())
except:
    print("N'a pas pu créer la Berline")

garage = GarageAutomobile(voitures, personnes)
# GarageAutomobile([], [])

print(len(garage.personnes))
print(len(garage.voitures))

Exercice 2

Soit le diagramme UML suivant:

Exemple UML

Apporter les modifications suivantes au diagramme:

  • L'être humain a un corps. Tout corps a un cerveau, des mains et un coeur.
  • Un animal est un être vivant qui a aussi un corps
  • Une plante est un être vivant (qui n'a pas de corps)

Exercice 3

Modéliser en UML les diagrammes de classe des cas suivants:

  • Une école
  • Un garage de mécanicien
  • Un jeu de combat en 1v1
  • Les exercices précédents

Une fois les diagrammes réalisés, coder les classes et instancier quelques objets.

Exercice 4

Développer une programme de gestion d’une médiathèque. On doit pouvoir stocker différents types de médias : audio, vidéo, texte (comme un livre ou un journal).

  • Les audio et vidéo peuvent être écoutés, le texte peut être lu et les vidéos peuvent être visualisées.
  • Chaque objet d’un de ces médias doit avoir au minimum un nom, un format (l'extension du fichier), une taille (en octets).
  • On veut pouvoir trier les médias selon le nom ou la taille par ordre croissant ou décroissant.
  • Un texte contient en plus une propriété "contenu" ainsi que deux méthodes supplémentaires, la première renvoie le nombre de consonnes du contenu, la deuxième le nombre de mots du contenu.

Questions :

  • 👉 Dessiner le diagramme de classes UML et coder les différentes classes.
  • 👉 Instancier une médiathèque contenant deux audios, une vidéo et trois textes. Vous êtes libre de valoriser les propriétés à votre guise. Vous pouvez vous faire aider de certains sites si besoin (générateur de texte, Générateur de nom de livre)

  • 👉 Afficher les médias triés par nom par ordre croissant et par taille par ordre décroissant.

  • 👉 Afficher le nombre de consonnes de tous les textes de la médiathèque
  • 👉 Afficher le nombre de consonnes de tous les textes de la médiathèque

On souhaite aussi stocker des textes audio qui sont des textes qu'on peut écouter.

  • 👉 Dessiner le nouveau diagramme UML de classes UML et mettre à jour votre code en conséquence.
  • 👉 Ajouter deux textes audio dans la médiathèque

On souhaite simuler l'interaction des humains avec la médiathèque. Chaque jour, une seule personne entre dans la médiathèque et ressort le même jour. Chaque personne effectue une seule action puis ressort. On suppose qu'il n'y a qu'une seule personne dans la médiathèque à la fois et peut effectuer une de ces actions aléatoirement:

  • Soit louer une vidéo disponible (non loué). Un film loué sera rendu après 10 jours,
  • Soit louer un texte ou un audio disponible (non loué). Un texte loué sera rendu dans 4 et un audio dans 5 jours,
  • Soit écouter une vidéo, un audio ou texte audio disponible (non loué).

  • 👉 Dessiner le diagramme de classes UML et coder les différentes classes.

  • 👉 Ecrire un programme qui réalise cette simulation pendant 1 mois (30 jours).
  • 👉 A la fin du mois, afficher des statistiques sur le nombre de films et textes loués ainsi que le nombre d'éléments écoutés.

Le propriétaire de la médiathèque permet de faire du CRUD (Create, Read, Update, Delete) sur la médiathèque. Une personne peut être propriétaire d'une seule médiathèques.

  • 👉 Dessiner le diagramme de classes UML et coder les différentes classes.
  • 👉 Développer les différentes méthodes CRUD
  • 👉 Effectuer ces actions via le propriétaire: ajouter une vidéo, afficher le contenu d'un texte, modifier un texte et supprimer un audio
Corrigé

solution uml mediathèque

import random
from typing import List


class Media:
    days_of_rent = 0

    def __init__(self, name, extension, size) -> None:
        self.name = name
        self.extension = extension
        self.size = size
        self.current_rented_days = 0

    def play(self):
        print("Ledcture de", self)

    def is_rented(self):
        return self.current_rented_days > 0

    def __str__(self) -> str:
        if self.is_rented():
            return f"{self.__class__.__name__} (name={self.name}, sera rendu dans {self.current_rented_days} jour(s)))"
        else:
            return f"{self.__class__.__name__} (name={self.name}, non loué)"

    def __repr__(self) -> str:
        return self.__str__()


class Video(Media):
    days_of_rent = 10

    def __init__(self, name, extension, size) -> None:
        super().__init__(name, extension, size)


class Audio(Media):
    days_of_rent = 5

    def __init__(self, name, extension, size) -> None:
        super().__init__(name, extension, size)


class Text(Media):
    days_of_rent = 4

    def __init__(self, name, extension, content: str) -> None:
        super().__init__(name, extension, len(content))
        self.content = content

    def get_word_count(self):
        return len(self.content.split(""))

    def get_consonant_count(self):
        count = 0
        for x in self.content:
            if x not in "aiueo":
                count += 1
        return count

    def get_consonant_count_2(self):
        return len([x not in "aiueo" for x in self.content])


class MediaLibrary:
    def __init__(self, medias: List[Media]) -> None:
        self.medias = medias

    def show_sorted_medias_by_size(self, descending=False):
        sorted_list = sorted(self.medias, key=lambda m: m.size, reverse=descending)
        print(f"tris par espace (décroissant {descending})", sorted_list)

    def show_sorted_medias_by_name(self, descending=False):
        sorted_list = sorted(self.medias, key=lambda m: m.name, reverse=descending)
        names = [x.name for x in sorted_list]
        print("noms triés (ordre décroissant {descending})", names)

    def __str__(self) -> str:
        return f"Contenu de la médiathèque: {self.medias}"

    def advance_rental(self):
        for media in self.medias:
            media.current_rented_days = max(media.current_rented_days - 1, 0)

    def get_all_consonant_count(self):
        counts = [x.get_consonant_count() for x in self.medias if isinstance(x, Text)]
        return sum(counts)


class Person:
    def __init__(self):
        pass

    def perform_random_action(self, mediatheque: MediaLibrary):
        action = random.randint(0, 2)
        if action == 0:
            videos = [
                x
                for x in mediatheque.medias
                if isinstance(x, Video) and not x.is_rented()
            ]
            if len(videos) == 0:
                print("Aucune vidéo disponible à la location")
                return
            r = random.randint(0, len(videos) - 1)
            videos[r].current_rented_days = Video.days_of_rent
            print("location de la vidéo", videos[r])
        if action == 1:
            audios_texts = [
                x
                for x in mediatheque.medias
                if (isinstance(x, Audio) or isinstance(x, Text)) and not x.is_rented()
            ]
            if len(audios_texts) == 0:
                print("Aucun media disponible à la location")
                return
            r = random.randint(0, len(audios_texts) - 1)
            audios_texts[r].current_rented_days = Audio.days_of_rent
            print("location du media", audios_texts[r])
        else:
            available_medias = [x for x in mediatheque.medias if not x.is_rented()]
            if len(available_medias) == 0:
                print("Auncun média disponible à la lecture")
                return
            r = random.randint(0, len(available_medias) - 1)
            available_medias[r].play()


class Owner(Person):
    def __init__(self, mediatheque: MediaLibrary):
        super().__init__()
        self.media_library = mediatheque

    def create_media(self, media):
        self.media_library.medias.append(media)

    def read_media(self, index):
        print("lecture du média", self.media_library.medias[index])
        return self.media_library.medias[index]

    def update_media(self, index, newValues):
        self.media_library.medias[index] = newValues

    def delete_media(self, index):
        self.media_library.medias.pop(index)


livre = Text("20000 lieux sous les mers", "epub", "plouf dans l'eau")

media_library = MediaLibrary(
    [
        Audio("Grand bleu", "mp3", 3_000_000),
        Audio("Chouzetsu Dynamic", "mp3", 3_000_000),
        Video("naruto last movie", "mp3", 1_000_000_000),
        livre,
        Text("Les 12 travaux d'Astérix", "epub", "12"),
        Text("Classroom of the elite", "pdf", "Ayanokoji Kyotaka"),
    ]
)

media_library.show_sorted_medias_by_name()
media_library.show_sorted_medias_by_size(True)

print("nombre de consonnes de", livre, "est", livre.get_consonant_count_2())
print(
    "nombre de consonnes dans la médiathèqye", media_library.get_all_consonant_count()
)

owner = Owner(media_library)
owner.create_media(Video("Spy x familiy code white", "mov", 2_000_000_000))
print(owner.media_library)
owner.delete_media(0)
print(owner.media_library)
owner.read_media(2)


print("simulation sur 30 jours")
for day in range(30):
    media_library.advance_rental()
    print("nouvelle journée")
    person = Person()
    person.perform_random_action(media_library)

print(media_library)