Programmation structurée

Syntaxe

En programmation structurée, le développeur définit des types :

NomType { nomAttribut1: typeAttribut1, nomAttribut2: typeAttribut2 … }

et des fonctions qui travaillent sur ces types :

Fonction nomFunction1 (nomParam1: NomType, nomParam2 : typeParam2 …);
Fonction nomFunction2 (nomParam1: NomType …): TypeRenvoyé;

Exemple en Python

from types import SimpleNamespace #pour créer des structures

# Définition du type :
Personne = type("Personne {nom: str, prenom: str}: ",(SimpleNamespace,),{})

# Définition des fonctions :
def creer_personne(nom: str, prenom: str) -> Personne:
    p = Personne()
    p.nom = nom
    p.prenom = prenom
    return p

def renommer_personne(p: Personne, nom: str, prenom: str) -> None:
    p.nom = nom
    p.prenom = prenom

def obtenir_nom_complet(p: Personne) -> str:
    return p.prenom + " " + p.nom

# Programme principal :
if __name__ == "__main__":
    p1 : Personne = creer_personne("MITI", "Tehau")
    p2 : Personne = creer_personne("KENHOLL", "Koridwen")
    print("Nom complet 1 :", obtenir_nom_complet(p1))
    renommer_personne(p1, "TEORIKA", "Mireis")
    print("Nom complet 1 (renommée) :", obtenir_nom_complet(p1))
    print("Nom complet 2 :", obtenir_nom_complet(p2))

Concepts de la programmation orientée objets

Classe et objet

Le principe fondamental de la POO est d’associer la définition des types structurés de données aux fonctions qui les manipulent dans des composants réutilisables et extensibles dénommés classes. Un objet désigne une variable dont le type est une classe.

Attribut et méthode

Une classe est une description abstraite des données et du comportement des objets, que l’on appelle instances de la classe. C’est une sorte de modèle :

  • de la structure statique / des caractéristiques des objets : les attributs (parfois appelés propriétés ou champs) ;
  • des comportements dynamiques, c’est-à-dire des traitements / actions / opérations réalisables sur les objets : les méthodes (fonctions ou procédures).

Encapsulation

Il convient de masquer les données des objets de la classe pour éviter les erreurs : le développeur d’une classe à la responsabilité de mettre à disposition des fonctions garantissant l’intégrité des objets. C’est le mécanisme de l’encapsulation.

L’encapsulation permet aussi de modifier l’organisation interne d’une classe (pour ajouter de nouvelles fonctionnalités, corriger des erreurs, améliorer les performances…) sans incidence pour les autres utilisateurs de cette classe, facilitant ainsi la maintenabilité des programmes.

C’est la visibilité des attributs et méthodes qui permet de mettre en œuvre l’encapsulation :

  • public (+) : les méthodes publiques sont accessibles depuis l’extérieur de la classe ; les attributs sont rarement publics ;
  • privé (-) ou protégé (#) : les attributs et méthodes privés sont encapsulés donc inaccessibles en dehors des méthodes la classe.

Les utilisateurs d’une classe manipulent les objets de la classe à travers les méthodes publiques de la classe.

La nuance entre privé et protégé sera étudiée ultérieurement (cours sur l’héritage).

Instanciation et constructeur

Au moment de l’instanciation (de la création) d’un nouvel objet de la classe :

  1. de la mémoire (RAM) est (automatiquement) allouée pour l’objet ;
  2. le constructeur de la classe est appelé : son rôle est d’initialiser les attributs de l’objet.

Le constructeur peut accepter des paramètres dont les valeurs seront affectés aux attributs de l’objet. En l’absence de paramètre qui lui est associé, un attribut pourra se voir affecter une valeur par défaut.

Manipulation d’objets

Les méthodes manipulent les objets de la classe. le mot clé self (en Python) ou this (Java, PHP…) fait référence à l’objet manipulé.

Les accesseurs (getters) et mutateurs (setters) sont des méthodes qui permettent respectivement de renvoyer (après éventuel formatage) ou modifier (en s’assurant de l’intégrité de l’objet) la valeur d’un attribut.

Langage UML

La définition d’une classe peut-être faîte en code (Java, C++, PHP…) ou sous la forme d’un diagramme UML de classes. UML est un langage de modélisation agnostique, c’est-à-dire non lié à un langage de programmation particulier.

Par convention :

  • le nom d’une classe est écrit en “PascalCase”, avec une majuscule au début de chaque partie du nom de la classe ; exemple : "LigneCommande" ;
  • les attributs sont écrits en minuscules ;
  • selon le langage, le nom des méthode est écrit en “PascalCase” ou “camelCase”, avec une minuscule au début à chaque partie du nom ; exemple : "getNom”.

Diagramme de classe UML

Syntaxe UML classes

Ce diagramme est utilisé pour la documentation développeur de la classe.

Diagramme de domaine UML

Ce diagramme est utile pour représenter la structure des données. Les comportements n’y figurent pas.

Syntaxe UML domaine

Un diagramme de domaine est assez similaire en finalité et forme à un MCD ; il y a néanmoins quelques différences :

  • il n’y a pas de concept d’attribut identifiant en POO ; l’annotation <<id>> est utilisée à cet effet sur le diagramme UML ;
  • il n’y a pas de concept de visibilité / d’encapsulation dans un MCD ;
  • les associations entre classes sont représentées par des objets, et non pas par des clés étrangères comme c’est le cas avec le MLD ;  exemple la classe Commande aura un attribut client de type (classe) Client et non pas un attribut idClient de type entier.
  • les cardinalités sont inversées, et * est utilisé à la place de n (plusieurs).

Diagramme d’API UML

Ce diagramme présente uniquement les attributs et méthodes publiques de la classe, autrement dit l’API. Il est destiné aux développeurs utilisateurs de la classe.

Syntaxe UML API

Exemple

Exemple de diagramme des classes UML

Syntaxe algorithmique

Définition d’une classe

classe NomClasse {
    public nomAttribut1: typeAttribut1;
    privé / protégé nomAttribut2: typeAttribut2;
    …

    constructeur(valeurAttribut1: typeAttribut1, …) {
        this.nomAttribut1 = valeurAttribut1;
        …
    }

    méthode getAttribut1(): TypeAttribut1 { //accesseur
        return this.nomAttribut1;
    }
    méthode setAttribut1(valeurAttribut1: TypeAttribut1) { //mutateur
        this.nomAttribut1 = valeurAttribut1;
    }
    méthode nomMéthode(…) {
        …
    }
    …
}

Utilisation d’une classe

var obj1 = new NomClasse(paramConstruct1, …); //instanciation
afficher(obj1.getAttribut1());
obj1.setAttribut1(NouvelleValeurAttribut1);
  • On part de l’objet pour appeler une de ses méthodes (exemple : getAttribut1).
  • Une méthode est une fonction (ou procédure si elle ne renvoie rien) : il faut mettre des parenthèses.
  • L’objet (ici obj1) devient this dans le corps de la méthode.

La référence à l’objet

En programmation structurée, les fonctions qui manipulent les “objets” prennent en paramètre une référence vers la variable. C’est aussi le cas des méthodes des langages orientés objets, même si ce paramètre n’apparait pas dans le profil de la méthode : la plupart des langages utilisent l’identificateur this (ceci) pour désigner l’objet manipulé par la méthode.

Ainsi pour une méthode :

classe NomClasse {
    …
    méthode nomMéthode(param1: TypeParam1…) {
        ceci.nomAttribut1 = …
        …
    }
}

il faut s’imaginer que son profil réel est :

classe NomClasse {
    …
    méthode nomMéthode(ceci: NomClasse, param1: TypeParam1…) {
        ceci.nomAttribut1 = …
        …
    }
}

et que l’appel obj.nomMéthode(param1…) est en réalité nomMéthode(obj, param1…).

Exemple en Java

Le Java est considéré comme une des références pour la syntaxe orientée objets.

Fichier “Personne.java"

public class Personne {
    private String nom;
    private String prenom;

    public Personne(String nom, String prenom) { //constructeur
        this.nom = nom;
        this.prenom = prenom;
    }

    public void renommer(String nom, String prenom) {
        this.nom = nom;
        this.prenom = prenom;
    }

    public String obtenirNomComplet() {
        return prenom + " " + nom;
    }
}

Fichier “Prog.java"

public class Prog {
    public static void main(String[] args) {
        var p1 = new Personne("MITI", "Tehau");
        var p2 = new Personne("KENHOLL", "Koridwen");
        p1.renommer("TEORIKA", "Mirei");
        System.out.println("Nom complet : " + p1.obtenirNomComplet());
        System.out.println("Nom complet : " + p2.obtenirNomComplet());
    }
}

Exécuter le programme

  • Pour compiler (bytecode) : javac Personne.java Prog.java
  • Pour exécuter : java Prog - exécution dans la JVM du JRE.

Exemple en Python

Le langage Python n’implémente de véritable mécanisme d’encapsulation : les attributs sont publics. Néanmoins, les conventions permettent de le simuler.

  • lorsque le nom d’un attribut commence par _, l’attribut est protégé ;
  • lorsque le nom d’un attribut commence par __, l’attribut est privé ; un mécanisme interne renomme l’attribut _NomClasse__nomAttribut.

De plus, par convention, c’est self (et non pas this) qui désigne l’objet manipulé, et il doit figurer en premier paramètre des méthodes.

Fichier “personne.py"

class Personne:
    _nom: str
    _prenom: str

    def __init__(self, nom: str, prenom: str): #constructeur
        self._nom = nom
        self._prenom = prenom

    def renommer(self, nom: str, prenom: str):
        self._nom = nom
        self._prenom = prenom

    def obtenirNomComplet(self) -> str:
        return self._prenom + " " + self._nom

Fichier “prog.py"

from personne import Personne

if __name__ == "__main__":
    p1 = Personne("MITI", "Tehau")
    p2 = Personne("KENHOLL", "Koridwen")
    p1.renommer("TEORIKA", "Mirei")
    print("Nom complet :", p1.obtenirNomComplet())
    print("Nom complet :", p2.obtenirNomComplet())

Exécuter le programme

  • python3 prog.py

Pour expérimenter, remplacer l’identificateur self par this partout dans le fichier Personne.py ; le programme fonctionne toujours, même si les conventions ne sont plus respectées…

Exemple en PHP

Fichier “Personne.php"

<?php
class Personne {
    protected string $nom;
    protected string $prenom;

    public function __construct(string $nom, string $prenom) {
        $this->nom = $nom;
        $this->prenom = $prenom;
    }

    public function renommer(string $nom, string $prenom): void {
        $this->nom = $nom;
        $this->prenom = $prenom;
    }

    public function obtenirNomComplet(): string {
        return $this->prenom . " " . $this->nom;
    }
}
?>

Fichier “prog.php"

<?php

require_once 'Personne.php';

$p1 = new Personne("MITI", "Tehau");
$p2 = new Personne("KENHOLL", "Koridwen");
$p1->renommer("TEORIKA", "Mirei");
echo "Nom complet : " . $p1->obtenirNomComplet() . "\n";
echo "Nom complet : " . $p2->obtenirNomComplet() . "\n";

Exécuter le programme

  • php prog.php
  • ou déployer sur un serveur web et ouvrir dans un navigateur la page “prog.php”