Quelques caractéristiques de la POO
Classes abstraites et Interfaces
- Une classe abstraite ou une interface ne peuvent être instanciés sauf via un objet anonyme (exemple en TS).
- Les classes abstraites et interfaces sont considérés comme des contrats.
- Une interface liste des membres sans implémentation.
- Une classe abstraite liste des membres avec ou sans implémentation.
- Une classe (abstraite ou non) peut hériter d'une seule classe (abstraite ou non) et de plusieurs interfaces.
- Certaines langages autorisent l'héritage multiple de classes.
- En typescript, un objet est compatible avec tout object qui a les mêmes champs sans avoir à explicitement implémenter son interface ou classe. On dit que c'est du duck typing; si ça marche et se comporte comme un canard, alors c'est un canard.
Classes abstraites et Interfaces
interface Shooter {
readonly shootPower: number;
shoot(): void;
}
interface Transporter {
readonly numberOfPeople: number;
}
abstract class Ship {
constructor(protected speed: number) {
console.log("Ship constructor");
}
abstract move(): void;
sayHello() {
console.log("Hello");
}
}
class SpaceShip extends Ship implements Shooter, Transporter {
constructor(
speed: number,
readonly staelliteCount: number,
readonly numberOfPeople: number,
readonly shootPower: number
) {
console.log("SpaceShip constructor before super");
super(speed);
console.log("SpaceShip constructor after super");
}
move(): void {
console.log("VROOOM");
}
shoot(): void {
console.log(`Shooting with power ${this.shootPower}`);
}
}
const spaceShip = new SpaceShip(1000, 5, 500, 10);
spaceShip.shoot();
console.log("The space ship has ", spaceShip.staelliteCount, " sattelites");
function checkPower(shooter: Shooter) {
console.log("The shooter has power of", shooter.shootPower);
}
checkPower(spaceShip);
const literalShooter: Shooter = {
shootPower: 6,
shoot() {
console.log("pew pew");
},
};
literalShooter.shoot();
checkPower(literalShooter);
Objets littéraux
- Certains langages permettent de créer des objets sans instancier une classe (non abstraite)
- Les propriétés et méthodes sont données directement lors de la définition de l'objet.
Objets littéraux
const ship1 = {
price: 20000,
name: "The decennial Eagle",
shootPower: 6,
shoot() {
console.log("pew pew");
},
};
ship1.shoot();
interface Shooter {
readonly shootPower: number;
shoot(): void;
}
function checkPower(shooter: Shooter) {
console.log("The shooter has power of", shooter.shootPower);
}
checkPower(ship1);
const serpent = {
shootPower: 1,
shoot() {
console.log("pew pew");
},
};
checkPower(serpent);
const cat = {
name: "chat-pardeur",
};
// checkPower(cat); // fails
Propriétés et accesseurs
- Une propriété permet d'accéder et / ou modifier une donnée de l'objet avec la syntaxe
objet.propriété
- Quand un propriété est utilisée en lecture, l'objet appelle une méthode qui retourne la valeur de la propriété. Cette méthode est appelée getter
- Quand on affecte une valeur à une propriété, l'objet appelle une méthode qui modifie la valeur propriété. Cette méthode est appelée setter
- Les getters et setters sont appelées accesseurs
- Certains langages gèrent nativement les accesseurs
- Dans la plupart des cas, une propriété repose sur un variable privée de la classe.
- On appelle ce genre de champ, un backing field
- Les langages qui gèrent nativement les propriétés utilisent un backing field par défaut et nous permettent de personnaliser les accesseurs par la suite.
- Les langages qui gèrent moins bien les propriétés laissent au développeur le soin de prévoir des méthodes getPropriété et setPropriété en avance.
Propriétés
class Mage {
// Deux propriétés dont le backing field est généré automatiquement
constructor(public name: string, public hp: number) {}
}
// La propriété name a des accesseurs peronnalisés
class OtherMage {
constructor(name: string, public hp: number) {
this._name = name;
}
// backing field
private _name: string;
get name() {
console.log("🧹 getting name", this._name);
return this._name;
}
set name(value) {
console.log("🧙 setting name with new value", value);
this._name = value.toLowerCase();
console.log("🧙🏾 name with new value", this._name);
}
}
const mage = new Mage("magus", 10);
mage.name = "magicus";
console.log(mage.name);
const otherMage = new OtherMage("sorcellus", 100);
otherMage.name = "sorcellicus";
console.log(otherMage.name);
Membres statiques
- Un membre statique d'une classe est utilisable sans créer d'instance particulière
- On peut appeler ou accéder à un membre statique depuis le nom de la classe. Certains langages permettent d'y accéder aussi depuis une instance.
- Les membres statiques peuvent être considérées comme des variables et fonctions globales, sauf qu'elles sont classées dans une classe
Propriétés
// Méthode globale (pas conseillée si on veut suivre l'orienté objet)
export function subsctract(x: number, y: number): number {
return x - y;
}
export const gravity = 10;
export class MathUtils {
// doit être appelé depuis la classe
static readonly PI = 3.14;
// doit être appelé depuis la classe
static add(x: number, y: number): number {
return x + y;
}
// doit être appelé depuis une instance
multiply(x: number, y: number): number {
return x * y;
}
}
console.log(MathUtils.PI);
console.log(MathUtils.add(2, 5));
const mu = new MathUtils();
console.log(mu.multiply(2, 5));
// erreur
//console.log(MathUtils.multiply(2, 5));
// On ne peut pas appeler du statique depuis un objet en TS
// console.log(mu.PI);
// console.log(mu.add(2, 5));
Polymorphisme
- Consiste à donner des comportement différents à une méthode selon la classe qui l'implémente et quand on appelle la méthode depuis la classe mère, on aura la méthode de sa classe concrète qui sera appelée
Propriétés
abstract class Shape {
abstract get area(): number;
showName() {
console.log("shape");
}
}
abstract class TwoDimensionShape extends Shape {
// surchage (ou override) de showName
showName() {
console.log("TwoDimensionShape");
}
}
class Rectangle extends TwoDimensionShape {
constructor(readonly width: number, readonly height: number) {
super();
}
get area() {
return this.width * this.height;
}
showName() {
console.log("Rectangle");
}
}
class Line extends Shape {
get area() {
return 0;
}
showName() {
console.log("Line");
}
}
const s: Shape = new Rectangle(10, 100);
console.log(s.area);
s.showName();
const shapes: Shape[] = [new Line(), new Rectangle(40, 80), new Line()];
// Si TS n'était pas polymorphe
for (const shape of shapes) {
if (shape instanceof Line) {
// casting: changer le type
(shape as Line).showName();
} else if (shape instanceof Rectangle) {
(shape as Rectangle).showName();
}
}
// mais comme TS est polymorhpe
for (const shape of shapes) {
shape.showName();
}
Exercices
Exo 1
Dans cet exercice, on manipule des formes géométriques que l'on définit par la classe abstraite GeometricShape
. Cette dernière déclare deux méthodes abstraites computeArea()
qui renvoie un nombre et computePerimeter()
qui retourne un nombre.
- Définir les classes
Rectangle
,Square
etCircle
qui héritent de la classeGeometricShape
. - Définir la classe
Drawing
(dessin en Anglais) qui contient une liste de formes géométriques. - Créer un programme qui instancie deux dessins avec chacun des formes géométriques différentes (4 formes environ par dessin).
Exo 2
Un fermier veut modéliser ses animaux via leurs caractéristiques. Certain animaux peuvent crier, d’autres sont muets. On représentera le fait de crier au moyen d’une méthode affichant à l’écran le cri de l’animal.
- Ecrire une interface
Shouter
(crieur en Anglais) contenant la méthode permettant de crier qu'on appellerashout()
. - Ecrire les classes des chats
Cat
, des chiensDog
et des lapinsRabbit
(qui sont muets) - Ecrire un programme avec un tableau pour les animaux qui savent crier, le remplir avec 1 chiens et deux chats, puis faire crier tous ces animaux. Décrire ce qui s’affiche à l’écran à l’exécution de ce programme.
- L'éleveur donne un nom à chacun de ses animaux. Créer la classe abstraite
Animal
avec la propriéténame
qui est la classe mère de tous les animaux. - Créer une classe
Farmer
(fermier en Français) qui contient un tableau d'animaux passé dans le constructeur. - Créer une instance de la classe
Farmer
en lui passant tableaux d'animaux contenant trois chiens, deux chats et trois lapins. - Le fermier souhaite retrouver facilement ses animaux pas leurs caractéristiques. Créer une méthode
findShoutingAnimals()
qui retourne un tableau des animaux du fermier qui crient (astuce: utiliserif ("shout" in animal)
pour savoir si un animal peut crier).- Pour info,
if ("shout" in animal)
n'est pas super propre mais c'est la méthode la plus simple pour débuter.
- Pour info,
- Le fermier souhaite passer par une propriété calculée à la place de la méthode de la question précédente. Créer une propriété calculée
get shoutingAnimals()
qui retourne le même résultat quefindShoutingAnimals()
. - Afficher le résultat de la méthode
findShoutingAnimals()
et la valeur de la propriétéget shoutingAnimals()
. Quelle syntaxe préférez-vous ? - Le fermier élève également un Canari (Canary en Anglais). Il souhaite en profiter pour modéliser les caractéristiques voler et marcher de ses animaux. Définir la classe
Canary
ainsi que les interfacesWalker
etFlyer
. Mettre à jour les classes existantes pour implémenter les interfaces qui leurs correspondent. - Créer une propriété calculée
get FlyingAnimals()
qui retourne une liste d'animaux qui volent.
Exo 3
- Créer une classe
Station
qui a deux champs en readonly:id
de type number etname
de type string. - Le constructeur ne prend que
name
en argument etid
est généré via un compteur qui est incrémenté à chaque nouvelle instance créé (astuce, utiliser une propriété statique) - créer trois stations et vérifier que leur id est bien positionné.
- définir une méthode
isEqualTo(s: Station)
qui retournetrue
si la station passée en argument à le mêmeid
- Faire quelques appels de
isEqualTo
sur différentes stations.
Exo 4
Créer une classe StringUtils qui définit les méthodes statiques suivantes:
askQuestion(message: string): string
: qui retourne une nouvelle chaîne de caractères qui est une concaténation un "?" àmessage
countVowels(message: string): number
qui retourne le nombre de voyelles dansmessage
removeVowels(message: string): string
qui retourne une nouvelle chaîne de caractères qui estmessage
dépourvu de ses voyelles (en gardant l'ordre des lettres initiales)