C01 2 Substitution
C01 2 Substitution
C01.2 : Substitution
MyClass
Classe mère
anOperation()
relation estUn
MySubClassC
anOperation()
anotherOpForC()
Rectangle
Longueur
largeur
Carre
cote
Carre
cote
• ...
class Rectangle:
def __init__(self, longueur=30, largeur=15): #Initialisateur
self.L, self.l = longueur, largeur
self.nom = ”rectangle”
def __str__(self): # toString()
return ”nom : {}”.format(self.nom)
...
class Carre(Rectangle): # héritage simple
”””Sous-classe spécialisée de la super-classe Rectangle.”””
def __init__(self, cote=20): #Initialisateur
# appel au constructeur de la super-classe de Carre :
super().__init__(cote, cote)
self.nom = ”carré” # surcharge d’attribut
...
• Certes mais attention : ces deux codes ne sont pas du tout équivalents !
• Le code Python est pythonesque ... c'est à dire malsain !
• Rien n'étant controlé, on peut créer des rectangles avec des longueurs négatives (cela
est certes possible mais pas en géométrie euclidienne !)
• L'utilisateur est fortement responsabilisé : de là le fameux slogan des développeurs
Python « We’re all consenting adults here » (nous sommes entre adultes consentants).
• Le code Java est sain
• Les domaines de définition des fonctions de mesure sont systématiquement vérifiés
• L'utilisateur a moins de responsabilité
• Il est possible d'écrire un code Python aussi sain que celui de Java : il sera alors beaucoup moins
pythonesque et beaucoup plus long !
• Même s'il restera légèrement plus court que son équivalent Java
C01.2 : Substitution
• Coder une fonction f : X →Y, y = f(x), consiste à coder une expression appelée règle « Si x = xi
alors y = yj » applicable à toutes valeurs xi du domaine de définition X de f :
• ∀xi, (xi∉X) ⇒ (∄ yj∈Y, y = yj)
• ∀xi∈X, (x = xi) ⇒ (∃yj∈Y, y = yj)
• Dans ce but, les langages informatiques ont introduit le test logique :
• if (xi∈X) then (∃yj∈Y, y = yj) else (∄ yj∈Y, y = yj)
• Cette forme est strictement équivalente aux deux règles Suivantes (KISS!) :
• Règle 1 : if (xi∉X) then (∄ yj∈Y, y = yj)
• Règle 2 : if (xi∈X) then (∃yj∈Y, y = yj)
• Le else des langages informatiques est donc a priori inutile
• En pratique, l’usage du else s'avère très dangereux parce qu'il masque la complexité
introduite par la négation d’un test logique
• La bonne manière de raisonner est de commencer par diagnostiquer la situation avant de prendre
une décision :
• int situation = 0 ;
• //Diagnosis
• if (condition1 == true) {
• situation = 1 ;
• }
• if ((condition1 == false) && (condition2 == true)) {
• situation = 2
• }
• //Decision
• if (situation == 0){
• //bloc exécuté dans la première situation
• }
• if (situation == 1){
• //bloc exécuté dans la deuxième situation
• }
• if (situation == 2){
• //bloc exécuté dans la troisième situation
• }
• La bonne manière de raisonner est de commencer par diagnostiquer la situation avant de prendre
une décision :
• int situation = 0 ;
• //Diagnosis
• if (condition1 == true) {
•
Il ne faut pas s'y tromper : cette approche Diagnosis/Decision est générale
situation = 1 ;
• }
•
et bien plus claire que les idiomes proposés par les langages qui sont aussi
if ((condition1 == false) && (condition2 == true)) {
• situation = séduisants
2 que trompeurs !
• }
•
La programmation
//Decision
par règle vaut pour n (ici 3) comme pour 2 et 1, ce qui
• if (situation == 0){
• n'est pas le //bloc
cas avec les dans
exécuté langages qui imposent
la première situation de changer d'idiome !
• }
• Mais l'intérêt fondamental
if (situation == 1){ de cette manière de coder des choix multiples est de
• //bloc exécuté dans la deuxième situation
•
permettre
}
de faire évoluer indépendamment les connaissances de diagnostique
• if (situation == 2){ de celles de décision !
• //bloc exécuté dans la troisième situation
• }
C01.2 : Substitution
• Java ayant été conçu pour un travail collaboratif distribué dans l'espace et dans le temps, la
factorisation de code est encouragée
• Tel est le rôle des classes abstraites : factoriser la déclaration d'attributs et de méthodes
• Les classes concrètes deviennent des composants : elles sont finales
//Default Constructor
protected Rectangle(Double length, Double width) {
this.lenght = length;
this.width = width;
}
//Factory Method
public static AbstractArea getInstance(Double side) {
//Check for domain values
if (side <= 0.0) {
System.out.println("getInstance(Double side= "+side+"), side is negative or null ==> return null!");
return null;
}
//All is ok ==> Build an instance
AbstractArea anAbstractArea = new BetterCarre(side);
return anAbstractArea;
}
//Constructor
private BetterCarre(Double side) {
super(side, side);
this.name = DEFAULT_NAME;
}
• Pour éviter la propagation des bugs, Java invite à la manipulation de types abstraits de
données définis par des interfaces
implements
interface Areable AbstractArea
//Constants
Abstract
Longueur
final Double LENGHT_DEFAULT_VALUE = 30.0d; largeur class
final Double WIDTH_DEFAULT_VALUE = 15.0d;
//Methods' signatures
extends
Double getLenght();
String getName();
Double getWidth();
Final classes
double getSurface();
•
public interface Areable {
//Constants
La signature d'une méthode est la donnée
//Default values de :
final Double LENGHT_DEFAULT_VALUE = 30.0d;
final Double WIDTH_DEFAULT_VALUE = 15.0d; • son type de retour
//Methods Signatures • son nom
Double getLenght();
• La liste ordonnée de ses paramètres
String getName(); formels typés
Double getWidth();
//Methods' signatures
extends
Double getLenght();
String getName();
Double getWidth();
Final classes
double getSurface();
//AbstractArea Attributes
protected Double lenght = Areable.LENGHT_DEFAULT_VALUE;
protected Double width = Areable.WIDTH_DEFAULT_VALUE;
protected String name = DEFAULT_NAME;
//Constructor
protected AbstractKissArea(Double length, Double width) {
this.lenght = length;
this.width = width;
}
implements
interface Areable AbstractArea
//Constants
Abstract
Longueur
final Double LENGHT_DEFAULT_VALUE = 30.0d; largeur class
final Double WIDTH_DEFAULT_VALUE = 15.0d;
//Methods' signatures
extends
Double getLenght();
String getName();
Double getWidth();
Final classes
double getSurface();
implements
interface Areable AbstractArea
//Constants
Abstract
Longueur
final Double LENGHT_DEFAULT_VALUE = 30.0d; largeur class
final Double WIDTH_DEFAULT_VALUE = 15.0d;
//Methods' signatures
extends
Double getLenght();
String getName();
Double getWidth();
Final classes
double getSurface();
//Methods' signatures
extends
Double getLenght();
String getName();
Double getWidth();
Final classes
double getSurface();
String getName();
return (this.width.equals(anotherAbstractArea.getWidth()));
Double getWidth();
}
int setLenght(Double length);
Rectangle Carre Losange
int setName(String name); ...
int setWidth(Double width);
}
Final classes
double getSurface();
//Methods' signatures
extends
Double getLenght();
//Constructor String getName();
Final classes
double getSurface();
}
©Marc LE GOC 2025 - Programmation Orientée Objet - Java 33/65
Idem pour la nouvelle version de la classe Carre : final class
KissCarre
//Constructor
private KissCarre(Double side) {
super(side, side); implements
interface Areable AbstractArea
this.name = DEFAULT_NAME; //Constants
Abstract
Longueur
largeur class
} final Double LENGHT_DEFAULT_VALUE = 30.0d;
//Methods' signatures
extends
//Getters and Setters Double getLenght();
String getName();
Double getWidth();
Final classes
double getSurface();
//Methods' signatures
}
extends
Double getLenght();
String getName();
Double getWidth();
Final classes
double getSurface();
L’interface est une abstraction des classes final qui définit un ensemble
de composants en intension
implements
interface Areable AbstractArea
//Constants
Abstract
Longueur
final Double LENGHT_DEFAULT_VALUE = 30.0d; largeur class
final Double WIDTH_DEFAULT_VALUE = 15.0d;
extends
//Methods' signatures
Double getLenght();
String getName();
Double getWidth();
Rectangle Carre Losange
int setLenght(Double length); ...
int setName(String name);
double getSurface();
Les classes final sont une réification de
l’interface, c-à-d un ensemble de composants
définit en extension
©Marc LE GOC 2025 - Programmation Orientée Objet - Java 36/65
Programmation Orientée Objet - Java
C01.2 : Substitution
• Lorsque l'on dit que « xi est un objet de la classe CX », on veut dire que la classe CX est
habilitée à créer des instances xi de l'interface IX de la classe CX
• Cela signifie que xi est un des éléments de l'ensemble X définit par les méthodes
déclarées dans l'interface IX
CX
IX
p1(x)
X ForAll xi obs_1 μ1 μ1(p1(xi))
p2(x)
obs_2 μ2 μ2(p2(xi))
...
p2(x)
obs_n μn μn(pn(xi))
μ1 μ1(p1)
μ2 μ2(p2)
X xi
... ...
μn μn(pn)
• La relation entre une instance et une classe est une relation de création
• Les classes sont seules habilités à créer des instances
• La relation entre une classe et une interface est une relation d'implémentation
• Une classe implémente au moins une interface
• La relation entre une instance et une interface est une relation d'appartenance
• xi∈X ⇔ xi estUn Xable ⇔ xi instanceof Xable
X IX X IX
IX
CX
μ1 μ1(p1)
μ2 μ2(p2)
CX X
... ...
xi
μn μn(pn)
x1 x2 ... xi x1 x2 ... xi
Concept générique
Polygonable AbstractPolygone
implements abstract class
extends
extends extends
implements implements
Aable ClassA Dable ClassD
extends extends
implements implements implements
Bable ClassB Iable ClassE
Eable
extends extends
implements implements
Cable ClassC Fable ClassF
extends extends
implements implements
Aable ClassA Dable ClassD
extends extends
implements implements implements
Bable ClassB Iable ClassE
Eable
extends extends
implements implements
Cable ClassC Fable ClassF
• Les comportements complexes, spéciaux ou exceptionnels sont codés dans des classes finalisées
(i.e. non héritables) qui se contentent d'implémenter des interfaces
• Le contrôleur ne connait que l'interface de son délégué, c'est-à-dire uniquement les
signatures des méthodes de l'interface Delegable
• Les classes finalisées implémentent Delegable indépendamment les unes des autres
aDelegable
Controler Delegable
anOperation() anOperation()
this.aDelegable.anOperation() ;
implements
//Delegable Section
@Override
public String getName() {
return this.name ;
}
@Override
public int anOperation() {
...
return 1 ;
}
aDelegable implements
Controler Delegable AbstractDelegate
anOperation() anOperation() anOperation()
this.aDelegable.anOperation() ;
extends
aDelegable
Controler Delegable
anOperation() anOperation() Schéma 1 :
this.aDelegable.anOperation() ; Délégation et
implements
Substitution sans
factorisation de code
Final ComponentA ComponentB ... ComponentI ... ComponentN
classes
aDelegable implements
Controler Delegable AbstractDelegate
anOperation() anOperation() anOperation()
Schéma 2 :
this.aDelegable.anOperation() ;
extends
Délégation et
Substitution avec
factorisation de code
Final ComponentA ComponentB ... ComponentI ... ComponentN
classes
aDelegable implements
Controler Delegable AbstractDelegate
this.aDelegable.anOperation() ;
this.aDelegable.anotherOperation() ; extends
classes
• Ce principe fondamental fait intervenir un utilisateur, ici une instance de Controler, qui veut
exécuter anOperation :
• Elle est déléguée à une classe concrète ComponentA ou ComponentB sans que
l'utilisateur sache forcément laquelle !
• L'opération anOperation du Controler est substituée par celle du délégué
• Une classe abstraite est utilisée pour factoriser le code commun à tous les composants
aDelegable implements
Controler Delegable AbstractDelagate
anOperation() anOperation() anOperation()
this.aDelegable.anOperation() ; extends
• Le contrôleur ne connait que le type abstrait de son délégué, c'est-à-dire les signatures des
méthodes de l'interface Delegable !
aDelegable
Controler Delegable
anOperation() anOperation()
implements
this.aDelegable.anOperation() ;
} System.out.println("changeDelegate(Areable anAreable=
"+anAreable.toString()+"), anAreable is equals to this
"+this.anAreable.toString()+" ==> Do noting and return 0!");
return 0;
}
//All is ok ==> change the delegate
this.anAreable = anAreable;
return 1;
}
} System.out.println("changeDelegate(Areable anAreable=
"+anAreable.toString()+"), anAreable is equals to this
Attention : il ne s'agit que d'une illustration, ici maladroite, des principes
"+this.anAreable.toString()+" ==> Do noting and return 0!");
return 0;
de délégation et de substitution qui sont à la base de la programmation }
d'aujourd'hui.
//All is ok ==> change the delegate
this.anAreable = anAreable;
return 1;
}
Ces principes privilégient la composition d'interface au détriment de
l'héritage. Nous reviendrons sur ces principes en 4ième année ... et dans le
}
TD3 !
C01.2 : Substitution
aDelegable
Controler Delegable
anOperation() anOperation() Schéma 1 :
this.aDelegable.anOperation() ; Délégation et
implements
Substitution sans
factorisation de code
Final ComponentA ComponentB ... ComponentI ... ComponentN
classes
aDelegable implements
Controler Delegable AbstractDelegate
anOperation() anOperation() anOperation()
Schéma 2 :
this.aDelegable.anOperation() ;
extends
Délégation et
Substitution avec
factorisation de code
Final ComponentA ComponentB ... ComponentI ... ComponentN
classes
aDelegable implements
Controler Delegable AbstractDelegate
anOperation() anOperation() anOperation()
anotherOperation()
this.aDelegable.anOperation() ;
extends
• Python est efficace pour développer des prototypes centrés sur l'analyse de données (cf.
https://covidtracker.fr/ par exemple)
• La programmeuse ou le programmeur Python est une ou un adulte consentant
• Sa responsabilité est pleine et entière !
• Le débugage des programmes Python est aussi pythonesque que sa syntaxe
• Les bugs ne sortent qu'à l'exécution !!!! Et ils ne sont pas facile à diagnostiquer !
• A la date de rédaction de ce transparent, le 31/01/2022, Python n'est pas ou peu intégrable
dans des applications temps réel, embarquées ou non
• Java est un langage qui impose à la programmeuse et au programmeur d'être pleinement
conscient des types abstraits des données qu'il manipule
• Le but de la programmeuse ou du programmeur habile est de contraindre le compilateur
à faire le maximum de vérifications a priori possible
• Il est ainsi possible de programmer en Java sans faire de bug
• C'est l'approche KISS, Keep It Simple, Stupid!
• Tout langage informatique propose un lexique, une grammaire et une sémantique pour coder :
• 1°) la fabrication xi∈X d'éléments xi d'un ensemble X définit en intension par une
interface Xable,
• 2°) l’assignation x=←xi d’une valeur xi à une variable x,
• 3°) le test de l'égalité x == xi entre deux valeurs et
• 4°) des règles « Si ... alors ... » du langage courant sous la forme de l’implication (x == xi)
⇒ (y←yj) d’une assignation y←yj à partir d’une égalité x == xi
this.aDelegable.anOperation() ; this.aDelegable.anOperation() ;
implements extends
Final ComponentA ComponentB ... ComponentI ... ComponentN Final ComponentA ComponentB ... ComponentI ... ComponentN
classes classes
this.aDelegable.anOperation() ; this.aDelegable.anOperation() ;
implements extends
Final ComponentA ComponentB ... ComponentI ... ComponentN Final ComponentA ComponentB ... ComponentI ... ComponentN
classes classes
Intension
Ensemble de propriétés
Abstraction
Fonctions
Structure Variable
Nombre
Comportement
Réification
Extension
Ensemble d’éléments E={ei}
this.aDelegable.anOperation() ; this.aDelegable.anOperation() ;
implements extends
Final ComponentA ComponentB ... ComponentI ... ComponentN Final ComponentA ComponentB ... ComponentI ... ComponentN
classes classes