Language TypeScript
LSI & ENSI
Pr. Lotfi ELAACHAK
Département Génie Informatique
2024 — 2025
Table des matières
1 Introduction 2
1.1 Caractéristiques principales de TypeScript . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Pourquoi TypeScript ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Installation de TypeScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Les bases de TypeScript 4
2.1 Les variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2 Les Tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.3 Union et Intersection TypeScript . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.4 Les Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.5 Les Classes et objets / POO sous TypeScript . . . . . . . . . . . . . . . . . . . . . 7
2.5.1 Classes et Objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.5.2 L’heritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.5.3 Les Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.6 Typage générique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.7 Signatures d’appel TypeScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.8 Modules et Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.9 Les Décorateurs / Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.9.1 Décorateur de Classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.9.2 Décorateur de Méthode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.9.3 Décorateur de Propriété . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3 Diagramme de classe vers code source TS 17
3.1 Association One To Many . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.2 Association Many To Many . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.3 Association One To One . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.4 Class d’associations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.5 Association Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4 Les Patrons de conception 23
4.1 Singleton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.2 Factory Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.3 Abstract Factory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.4 Prototype . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.5 Decorator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.6 Observer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.7 Dependency Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1
Chapitre 1
Introduction
Le TypeScript est un langage très récent (février 2012) qui a été conçu par Anders Hejlsberg,
également concepteur du langage C#. Le but premier de TypeScript est de rendre plus facile et
plus fiable l’écriture de code en JavaScript pour des applications de grande ampleur.
TypeScript permet un typage statique optionnel des variables et des fonctions, la création de
classes et d’interfaces, l’import de modules, tout en conservant l’approche non-contraignante de
JavaScript. Il supporte la spécification ECMAScript 6.
Voire : [Link]
Pour l’exécution de type script sur la machine il faut installer node js, après il faut installer le
compilateur type script « tsc » :
Pourquoi utiliser TypeScript plutôt que JavaScript ?
Alors que JavaScript est excellent pour le prototypage rapide, le typage statique de TypeScript
favorise une meilleure organisation et une meilleure collaboration dans les projets complexes. Il
fournit des outils de vérification des erreurs et une meilleure prise en charge des IDE, ce qui
facilite la gestion de grandes bases de code.
1.1 Caractéristiques principales de TypeScript
1. Typage statique : Le système de type TypeScript permet de détecter les erreurs au moment
de la compilation, ce qui réduit les erreurs d’exécution et améliore la fiabilité du code.
2. Support IDE amélioré : TypeScript offre une excellente intégration avec les environnements
de développement intégrés (IDE) les plus répandus comme Visual Studio Code, offrant
des fonctionnalités telles que l’autocomplétion, le refactoring, et bien plus encore.
3. Amélioration de la lisibilité et de la maintenabilité du code : Les définitions de types et
les interfaces rendent le code plus compréhensible et plus facile à maintenir.
4. Amélioration de la productivité des développeurs : Des fonctionnalités telles que l’inférence
de type, les fonctionnalités de type avancées et la prise en charge du JavaScript moderne
(ES6+) rationalisent le développement et stimulent la productivité.
5. Interfaces : Définir des contrats pour les objets, en s’assurant qu’ils disposent de propriétés
et de méthodes spécifiques.
6. Classes : Encapsulent les données et les fonctionnalités selon les principes de l’orientation
objet.
7. Génériques : Créer des composants réutilisables qui peuvent fonctionner avec différents
types de données.
8. Modules : Organisent le code en unités réutilisables pour une meilleure maintenabilité.
9. Décorateurs : ajoutent des métadonnées aux classes et aux fonctions pour une personna-
lisation avancée.
2
1.2 Pourquoi TypeScript ?
— Applications Web à grande échelle : Le typage statique de TypeScript favorise une meilleure
organisation et une meilleure collaboration dans les projets complexes.
— Applications d’entreprise : La vérification robuste des erreurs et la clarté du code amé-
liorent la fiabilité et la maintenabilité des systèmes critiques.
— Cadres frontaux : De nombreux frameworks populaires comme Angular et React adoptent
TypeScript pour ses avantages en matière de sécurité des types.
— Amélioration de la lisibilité : Les annotations de type claires dans TypeScript rendent le
code plus lisible. Les développeurs peuvent facilement comprendre l’objectif des variables
et des fonctions, ce qui permet d’améliorer la collaboration et la maintenabilité.
— Approfondissement de la compréhension de JavaScript : Même si vous n’utilisez pas TypeS-
cript de manière intensive, son apprentissage permet d’approfondir votre compréhension
de JavaScript. Appréciez les caractéristiques du langage et leur impact sur la qualité du
code, que vous écriviez en TypeScript ou en JavaScript.
— Développement de logiciels robustes :TypeScript ne rendra pas votre logiciel exempt de
bogues, mais il peut prévenir de nombreuses erreurs liées au [Link] les projets à grande
échelle, l’adoption de TypeScript permet d’obtenir des logiciels plus robustes qui peuvent
encore être déployés là où les applications JavaScript ordinaires fonctionnent.
1.3 Installation de TypeScript
Pour l’exécution de type script sur la machine il faut installer node js, après il faut installer le
compilateur type script « tsc » :
npm install -g typescript
tsc –version
Hello World
let message: string = 'Hello World';
[Link](message);
Pour compiler votre code TypeScript, vous pouvez ouvrir le terminal intégré et taper tsc hello-
[Link]. Cela compilera et créera un nouveau fichier JavaScript [Link].
ELAACHAK Lotfi Page 3
Chapitre 2
Les bases de TypeScript
2.1 Les variables
Le principal apport du langage TypeScript, celui qui justifie le nom même du langage, est la
possibilité d’associer, facultativement, un type à une donnée.
Le Typage explicite
var pi: number;
var message: string;
var flag: boolean;
var joker: any;
Dans l’exemple ci-dessus, quatre variables sont déclarées sans être initialisées à l’aide d’un type
dont la signification est explicite.
— La variable pi a pour type number, un nombre entier ou flottant.
— La variable message a pour type string, une chaîne de caractères.
— La variable flag a pour type boolean, un booléen qui peut prendre la valeur true ou false.
— La variable joker a pour type any, qui est le type par défaut qu’attribue
Le Typage implicite
var pi = 3.14; // number
var message = "Bonjour !"; // string
var flag = true; // boolean
var joker = null; // any
Lors de la première initialisation d’une variable, TypeScript en infère automatiquement le type
sans qu’il soit nécessaire de le mentionner explicitement. Ainsi, TypeScript, contrairement à Ja-
vaScript, peut être considéré comme un langage à typage statique.
Data Type Keyword Description
Number number It is used to represent both Integer as well as Floating-
Point numbers.
Boolean boolean Represents true and false.
String string It is used to represent a sequence of characters.
Void void Generally used on function return-types.
Null null It is used when an object does not have any value.
Undefined undefined Denotes value given to an uninitialized variable.
Any any If a variable is declared with any data type, then any
type of value can be assigned to that variable.
4
2.2 Les Tableaux
Un tableau est un type de données défini par l’utilisateur. Un tableau est une collection homogène
d’éléments de même type qui disposent d’un emplacement mémoire contigu et qui peuvent stocker
plusieurs valeurs de différents types de données.
Tableau
let fruits: string[] = ['Apple', 'Orange', 'Banana'];
for (let index in fruits) {
[Link](fruits[index]); // output: Apple Orange Banana
}
for (let i = 0; i < [Link]; i++) {
[Link](fruits[i]); // output: Apple Orange Banana
}
let arr1 = [ 1, 2, 3];
let arr2 = [ 4, 5, 6];
// Create new array from existing array
let copyArray = [...arr1];
[Link]("CopiedArray: " +copyArray);
// Create new array from existing array with more elements
let newArray = [...arr1, 7, 8];
[Link]("NewArray: " +newArray);
// Create array by merging two arrays
let mergedArray = [...arr1, ...arr2];
[Link]("MergedArray: " +mergedArray);
// matrix
let mArray:number[][] = [[10, 20, 30], [50, 60, 70]] ;
[Link](mArray[0][0]);
[Link](mArray[0][1]);
[Link](mArray[0][2]);
[Link]();
[Link](mArray[1][0]);
[Link](mArray[1][1]);
[Link](mArray[1][2]);
2.3 Union et Intersection TypeScript
L’union TypeScript permet de combiner un ou deux types de données différents (par exemple, un
nombre, une chaîne, un flotteur, un double, etc.) C’est le moyen le plus puissant d’exprimer une
ELAACHAK Lotfi Page 5
variable avec plusieurs types. Utilisez le symbole de la pipe (’|’) pour combiner deux ou plusieurs
types de données afin d’obtenir le type Union.
Union Type
let value: number | string;
value = 190;
[Link]("Numeric value of the value: "+value);
value = "Welcome to TypeScript!";
[Link]("String value of the value: "+value);
Types d’intersection (combinaison de plusieurs types)
Intersection Type
type Employee = { name: string; id: number };
type Manager = { department: string };
type ManagerEmployee = Employee & Manager;
const boss: ManagerEmployee = { name: "John", id: 101, department: "HR" };
2.4 Les Fonctions
Il est courant qu’une fonction renvoie un certain résultat. Le langage TypeScript permet de pré-
ciser le type du résultat attendu lors de la déclaration de la fonction.
Par défaut et en l’absence d’instruction return, le type du résultat d’une fonction est void, c’est-
à-dire aucun résultat.
Fonctions
function triple(n: number): number {
return 3 * n;
}
[Link](triple(3));
function displayType(geeks: (string | number)) {
if(typeof(geeks) === "number")
[Link]('geeks is number.')
else if(typeof(geeks) === "string")
[Link]('geeks is string.')
}
ELAACHAK Lotfi Page 6
// Output: Code is number.
displayType(49);
// Output: Code is string.
displayType("GFG");
// Compiler Error: Argument of type 'true' is not
// assignable to a parameter of type string | number
displayType(true);
2.5 Les Classes et objets / POO sous TypeScript
2.5.1 Classes et Objets
La notion de classe introduite dans TypeScript anticipe la prochaine évolution de JavaScript
(ECMAScript 6).
Les Classes / Objets
class Animal {
private name: string;
constructor(name: string) {
[Link] = name;
}
shout(): string {
return "...";
}
}
var animal = new Animal("pokemon");
class User {
private _firstName: string = '';
private _lastName: string = '';
// Setter for first name
set firstName(name: string) {
this._firstName = name;
}
// Setter for last name
set lastName(name: string) {
this._lastName = name;
ELAACHAK Lotfi Page 7
}
// Getter to display full name
get fullName(): string {
return `${this._firstName} ${this._lastName}`;
}
}
const user = new User();
[Link] = "Hitangshu";
[Link] = "Agarwal";
[Link]([Link]);
2.5.2 L’heritage
De pair avec la notion de classe, TypeScript implémente la notion d’héritage simple par l’utili-
sation du mot-clé extends.
L’extension de la classe Animal de l’exemple précédent pourrait se faire ainsi :
Les Classes Heritage
class Lion extends Animal {
sex: string;
constructor(name: string, sex: string) {
super(name);
[Link] = sex;
}
shout(): string {
return "Rooooaarrr!"
}
}
class Person {
name: string;
rollNumber: number;
score: number;
constructor(name: string, rollNumber: number, score: number) {
[Link] = name;
[Link] = rollNumber;
[Link] = score;
}
ELAACHAK Lotfi Page 8
displayDetails(): void {
[Link](`Name: ${[Link]},
Roll Number: ${[Link]},
Scores: ${[Link]} out of 100`);
}
}
class Student extends Person {
constructor(name: string, rollNumber: number, score: number) {
super(name, rollNumber, score);
}
displayDetails(): void {
[Link]();
[Link](`${[Link]} is an intelligent boy..`);
[Link](`${[Link]} scores well...`);
}
}
const student = new Student('Rohit', 2, 96);
[Link]();
Puisque toutes les classes définies dans TypeScript sont considérées comme de nouveaux types,
la classe Lion est du type Lion, et en vertu de l’héritage est aussi du type Animal.
En remarque, malgré cet apport orienté objet à la syntaxe initiale de JavaScript, il faut avoir
conscience que les limitations intrinsèques de JavaScript se reflètent également dans TypeScript.
Par exemple, la notion d’attribut privé (private) d’une classe qui existe dans la plupart des lan-
gages orientés objet, bien que syntaxiquement présente dans TypeScript, n’est pas véritablement
restrictive dans la mesure où un attribut privé pourra malgré tout être utilisé en dehors de sa
classe avec une approche dynamique.
2.5.3 Les Interfaces
Une interface peut être vue tout d’abord comme une sorte de contrat minimum que doit respecter
une structure de données en termes d’attributs et de méthodes. Cette structure de données peut
être un objet . . . ou une classe.
Les Interfaces
interface I1 {
a: number;
}
interface I2 {
b: string;
}
ELAACHAK Lotfi Page 9
class C implements I1, I2 {
a: number;
b: string;
constructor(a: number, b: string) {
this.a = a;
this.b = b;
}
}
Une interface est considérée comme un type à part entière, ce qui signifie qu’il est possible de
définir une fonction qui prendrait un paramètre de type I1 comme l’exemple ci-dessous :
Les Interfaces
function fct(x: I1) {
alert(x.a);
}
\\
fct({ a: 20, z: -1 });
Enfin, comme nous venons de le voir, une interface pouvant être considérée comme une classe
abstraite, il est possible de faire hériter une interface d’une autre à l’aide du mot-clé extends :
Les Interfaces
interface I3 extends I2 {
c: boolean;
}
2.6 Typage générique
Les types d’objets génériques TypeScript vous permettent de créer des définitions de types
flexibles et réutilisables pour les objets. Ces types génériques peuvent fonctionner avec différentes
formes d’objets tout en assurant la sécurité des types, ce qui garantit la robustesse et l’adapta-
bilité de votre code. Ils sont particulièrement utiles pour créer des fonctions ou des classes qui
peuvent gérer différentes structures d’objets tout en conservant l’exactitude du type.
Les Types Generiques
type KeyValuePair<T> = {
key: string;
value: T;
};
const stringPair: KeyValuePair<string> = { key: 'name', value: 'John' };
const numberPair: KeyValuePair<number> = { key: 'age', value: 30 };
[Link](stringPair);
[Link](numberPair);
ELAACHAK Lotfi Page 10
Les Types Generiques
function concatenate<T>(a1: T[], a2: T[]): T[] {
return [Link](a2);
}
resultNumbers = concatenate<number>([1, 2], [3, 4]); // [1, 2, 3, 4]
resultStrings = concatenate<string>(["a", "b"], ["c", "d"]); // ["a", "b", "c", "d
"]
resultError1 = concatenate<number>([1, 2], ["a", "b"]); // erreur
resultError2 = concatenate<string>([1, 2], ["a", "b"]); // erreur
resultAny = concatenate<any>([1, 2], ["a", "b"]); // [1, 2, "a", "b"]
2.7 Signatures d’appel TypeScript
Les signatures d’appel TypeScript définissent des objets qui peuvent être invoqués comme des
fonctions mais qui possèdent également des propriétés. Elles spécifient les paramètres et le type de
retour pour le comportement de la fonction. Elles peuvent inclure des propriétés supplémentaires,
ce qui permet de créer des objets appelables polyvalents qui agissent à la fois comme des fonctions
et des conteneurs de données.
Signatures TypeScript
type MyObject = {
(input: string): string; // Call signature
propertyName: string; // Property
};
const myObject: MyObject = (input: string) => {
return `Hello, ${input}!`;
};
[Link] = "Greeting Function";
[Link](myObject("World")); // Output: Hello, World!
[Link]([Link]); // Output: Greeting Function
Signatures TypeScript
type Calculator = {
(a: number, b: number): number; // Call signature
operation: string; // Property
};
const add: Calculator = (a: number, b: number) => a + b;
[Link] = "Addition";
const multiply: Calculator = (a: number, b: number) => a * b;
[Link] = "Multiplication";
ELAACHAK Lotfi Page 11
[Link](`Operation: ${[Link]}, Result: ${add(5, 3)}`);
[Link](`Operation: ${[Link]}, Result: ${multiply(5, 3)}`);
Les signatures de construction TypeScript définissent la forme d’une fonction de construction,
en spécifiant les paramètres qu’elle attend et le type d’objet qu’elle construit. Elles utilisent le
mot-clé new dans une déclaration de type pour garantir l’instanciation correcte des classes ou
des objets.
Signatures Constructor
// Define a class for
// representing books
class Book {
constructor(
public title: string, public author: string) { }
}
// Define a construct
// signature for the Book class
type BookConstructor =
new (title: string, author: string) => Book;
// Create a factory function based
// on the construct signature
function createBook(BookConstructor: BookConstructor,
title: string, author: string) {
return new BookConstructor(title, author);
}
// Use the factory function
// to create a book instance
const bookInstance = createBook(Book,
"The Story of My Experiments", "Mahatma Gandhi");
// Log the book's properties
[Link]("Title:", [Link]);
[Link]("Author:", [Link]);
2.8 Modules et Namespaces
Un module permet de diviser le code en plusieurs fichiers et d’importer uniquement ce qui est
nécessaire.
Module
// [Link]
export function addition(a: number, b: number): number {
ELAACHAK Lotfi Page 12
return a + b;
}
export function multiplication(a: number, b: number): number {
return a * b;
}
// [Link]
import { addition, multiplication } from "./mathUtils";
[Link](addition(3, 5)); // 8
[Link](multiplication(4, 6)); // 24
Les espaces de noms (namespace) sont une ancienne façon d’organiser le code en groupant des
objets, classes ou fonctions sous un même nom. Un namespace permet d’éviter les conflits de
noms en encapsulant des éléments dans un contexte.
Namespace
namespace MathUtils {
export function addition(a: number, b: number): number {
return a + b;
}
export function multiplication(a: number, b: number): number {
return a * b;
}
}
[Link]([Link](3, 5)); // 8
[Link]([Link](4, 6)); // 24
2.9 Les Décorateurs / Decorators
Les décorateurs en TypeScript sont une fonction spéciale qui peut être attachée à une classe,
une méthode, une propriété ou un paramètre pour ajouter un comportement supplémentaire. Un
décorateur est une fonction précédée du symbole @, qui s’exécute avant la définition de l’élément
décoré.
TypeScript propose plusieurs types de décorateurs :
— Décorateur de Classe
— Décorateur de Méthode
— Décorateur de Propriété
Pour utiliser le decorateur il faut creer le fichier [Link] a travers la cmd : tsc -init puis
recofigurer le ficher en ajoutant ces deux lignes :
ELAACHAK Lotfi Page 13
"experimentalDecorators" : true,
"emitDecoratorMetadata" : true,
2.9.1 Décorateur de Classe
Un décorateur de classe est déclaré juste avant la déclaration d’une classe. Le décorateur de
classe est appliqué au constructeur de la classe et peut être utilisé pour observer, modifier ou
remplacer une définition de classe. Un décorateur de classe ne peut pas être utilisé dans un fichier
de déclaration, ou dans tout autre contexte ambiant (tel que sur une classe déclarée).
L’expression du décorateur de classe sera appelée comme une fonction au moment de l’exécution,
avec le constructeur de la classe décorée comme seul argument.
Si le décorateur de classe renvoie une valeur, il remplacera la déclaration de la classe par la
fonction de construction fournie.
Class Decorator
function Logger(constructor: Function) {
[Link]("Classe créée :", [Link]);
}
@Logger
class Person {
constructor(public name: string) {}
}
const p1 = new Person("Alice");
— @Logger est appelé lorsque la classe Person est définie.
— Il affiche un message dans la console.
2.9.2 Décorateur de Méthode
Un décorateur de méthode est déclaré juste avant la déclaration d’une méthode. Le décorateur
est appliqué au descripteur de propriété de la méthode et peut être utilisé pour observer, modifier
ou remplacer une définition de méthode. Un décorateur de méthode ne peut pas être utilisé dans
un fichier de déclaration, sur une surcharge ou dans tout autre contexte ambiant (comme dans
une classe déclarée).
Method Decorator
function deco_method(flag : string ) {
return function (originalMethod: any, _context: any) {
//[Link] = value;
[Link]("greeting : " + flag);
ELAACHAK Lotfi Page 14
[Link]("je suis le dicorator method : " + originalMethod);
[Link]("je suis le dicorator context : " + [Link]( _context) );
};
}
class Greeter {
greeting: string = "test";
constructor(message: string) {
//[Link] = message;
}
@deco_method("msg")
greet() {
return "Hello, " + [Link];
}
@deco_method("msg")
manger() {
return "je mange , " + [Link];
}
}
const greeter = new Greeter("world");
[Link]([Link]());
— @LogExecutionTime mesure le temps d’exécution de la méthode sum().
— Il remplace la méthode originale par une version qui affiche le temps pris.
2.9.3 Décorateur de Propriété
Un décorateur de propriété permet de modifier un attribut avant son utilisation. Le décorateur
de propriété est appliqué au descripteur de propriété de l’accesseur et peut être utilisé pour
observer, modifier ou remplacer les définitions d’un accesseur. Un décorateur d’accesseur ne peut
pas être utilisé dans un fichier de déclaration, ou dans tout autre contexte ambiant (comme dans
une classe de déclaration).
Property Decorator
function doSomthing() {
return function (originalMethod: any, _context: any) { // Use a getter to make
the property read-only
[Link](" deco attribut " + [Link](_context));
};
}
class Utilsaiteur {
ELAACHAK Lotfi Page 15
@doSomthing()
name = "Alice";
@doSomthing()
prenom = "test" ;
}
const u1 = new Utilsaiteur();
— @Readonly empêche la modification de name après sa définition.
ELAACHAK Lotfi Page 16
3
Chapitre
Diagramme de classe vers code source TS
3.1 Association One To Many
One To Many
class A {
private listb: B[] = []; // Array of B instances
// Getter for `listb`
public getListb(): B[] {
return [Link];
}
// Setter for `listb`
public setListb(listb: B[]): void {
[Link] = listb;
}
}
class B {
private a: A | null = null; // Reference to an instance of A, or null
// Getter for `a`
public getA(): A | null {
return this.a;
}
// Setter for `a`
public setA(a: A): void {
this.a = a;
17
}
}
3.2 Association Many To Many
Many To Many
class A {
private listb: B[] = []; // Array of B instances
// Getter for `listb`
public getListb(): B[] {
return [Link];
}
// Setter for `listb`
public setListb(listb: B[]): void {
[Link] = listb;
}
}
class B {
private lista: A[] = []; // Array of A instances
// Getter for `lista`
public getListA(): A[] {
return [Link];
}
// Setter for `lista`
public setListA(lista: A[]): void {
[Link] = lista;
}
}
ELAACHAK Lotfi Page 18
3.3 Association One To One
One To One
class A {
// Class A can have properties and methods as needed
}
class B {
private a: A | null = null; // TypeScript typically initializes as null if
necessary
// Getter for `a`
public getA(): A | null {
return this.a;
}
// Setter for `a`
public setA(a: A): void {
this.a = a;
}
}
ELAACHAK Lotfi Page 19
3.4 Class d’associations
Association Class
class A {
private listb: B[] = []; // Array of B instances
// Getter for `listb`
public getListb(): B[] {
return [Link];
}
// Setter for `listb`
public setListb(listb: B[]): void {
[Link] = listb;
}
}
class B {
private lista: A[] = []; // Array of A instances
// Getter for `lista`
public getListA(): A[] {
return [Link];
}
// Setter for `lista`
public setListA(lista: A[]): void {
[Link] = lista;
}
}
class C {
private a: A | null = null; // Reference to an instance of A or null
private b: B | null = null; // Reference to an instance of B or null
ELAACHAK Lotfi Page 20
// Getter for `a`
public getA(): A | null {
return this.a;
}
// Setter for `a`
public setA(a: A): void {
this.a = a;
}
// Getter for `b`
public getB(): B | null {
return this.b;
}
// Setter for `b`
public setB(b: B): void {
this.b = b;
}
}
3.5 Association Composition
Composition
class A {
private listb: B[]; // Array of B instances
// Constructor initializing listb
constructor() {
[Link] = []; // Equivalent to Java's `new ArrayList<>();`
}
ELAACHAK Lotfi Page 21
// Getter for `listb`
public getListb(): B[] {
return [Link];
}
// Setter for `listb`
public setListb(listb: B[]): void {
[Link] = listb;
}
}
class B {
private a: A | null = null; // Reference to an instance of A, or null
// Getter for `a`
public getA(): A | null {
return this.a;
}
// Setter for `a`
public setA(a: A): void {
this.a = a;
}
}
ELAACHAK Lotfi Page 22
Chapitre 4
Les Patrons de conception
Les Patrons de conception sont des solutions typiques à des problèmes courants de conception
de logiciels. Ils sont comme des plans préfabriqués que vous pouvez personnaliser pour résoudre
un problème de conception récurrent dans votre code.
Vous ne pouvez pas simplement trouver un modèle et le copier dans votre programme, comme
vous pouvez le faire avec des fonctions ou des bibliothèques prêtes à l’emploi. Le modèle n’est pas
un morceau de code spécifique, mais un concept général pour résoudre un problème particulier.
Vous pouvez suivre les détails du modèle et mettre en œuvre une solution adaptée aux réalités
de votre propre programme.
Les Patrons sont souvent confondus avec les algorithmes, car les deux concepts décrivent des
solutions typiques à des problèmes connus. Alors qu’un algorithme définit toujours un ensemble
clair d’actions permettant d’atteindre un certain objectif, un modèle est une description de plus
haut niveau d’une solution. Le code d’un même modèle appliqué à deux programmes différents
peut être différent.
En outre, tous les modèles peuvent être classés en fonction de leur intention ou de leur but. Ce
livre couvre trois groupes principaux de modèles :
— Les modèles de création fournissent des mécanismes de création d’objets qui augmentent
la flexibilité et la réutilisation du code existant.
— Les modèles structurels expliquent comment assembler des objets et des classes dans des
structures plus grandes, tout en gardant ces structures flexibles et efficaces.
— Les modèles comportementaux s’occupent de la communication efficace et de l’attribution
des responsabilités entre les objets.
4.1 Singleton
Le singleton est un modèle de création qui permet de s’assurer qu’une classe n’a qu’une seule
instance, tout en fournissant un point d’accès global à cette instance.
Le modèle Singleton résout deux problèmes en même temps, violant ainsi le principe de la res-
ponsabilité unique :
23
Singleton
class Singleton {
static #instance: Singleton;
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
private constructor() { }
/**
* The static getter that controls access to the singleton instance.
*
* This implementation allows you to extend the Singleton class while
* keeping just one instance of each subclass around.
*/
public static get instance(): Singleton {
if (!Singleton.#instance) {
Singleton.#instance = new Singleton();
}
return Singleton.#instance;
}
/**
* Finally, any singleton can define some business logic, which can be
* executed on its instance.
*/
public someBusinessLogic() {
// ...
}
}
/**
ELAACHAK Lotfi Page 24
* The client code.
*/
function clientCode() {
const s1 = [Link];
const s2 = [Link];
if (s1 === s2) {
[Link](
'Singleton works, both variables contain the same instance.'
);
} else {
[Link]('Singleton failed, variables contain different instances.');
}
}
clientCode();
[Link]: Execution result
Singleton works, both variables contain the same instance.
4.2 Factory Method
La méthode Factory est un modèle de création qui fournit une interface pour la création d’objets
dans une superclasse, mais qui permet aux sous-classes de modifier le type d’objets qui seront
créés.
ELAACHAK Lotfi Page 25
Factory Method
**
* The Creator class declares the factory method that is supposed to return an
* object of a Product class. The Creator's subclasses usually provide the
* implementation of this method.
*/
abstract class Creator {
/**
* Note that the Creator may also provide some default implementation of the
* factory method.
*/
public abstract factoryMethod(): Product;
/**
* Also note that, despite its name, the Creator's primary responsibility is
* not creating products. Usually, it contains some core business logic that
* relies on Product objects, returned by the factory method. Subclasses can
* indirectly change that business logic by overriding the factory method
* and returning a different type of product from it.
*/
public someOperation(): string {
// Call the factory method to create a Product object.
const product = [Link]();
// Now, use the product.
return `Creator: The same creator's code has just worked with ${product.
operation()}`;
}
}
/**
ELAACHAK Lotfi Page 26
* Concrete Creators override the factory method in order to change the
* resulting product's type.
*/
class ConcreteCreator1 extends Creator {
/**
* Note that the signature of the method still uses the abstract product
* type, even though the concrete product is actually returned from the
* method. This way the Creator can stay independent of concrete product
* classes.
*/
public factoryMethod(): Product {
return new ConcreteProduct1();
}
}
class ConcreteCreator2 extends Creator {
public factoryMethod(): Product {
return new ConcreteProduct2();
}
}
/**
* The Product interface declares the operations that all concrete products must
* implement.
*/
interface Product {
operation(): string;
}
/**
* Concrete Products provide various implementations of the Product interface.
*/
class ConcreteProduct1 implements Product {
public operation(): string {
return '{Result of the ConcreteProduct1}';
}
}
class ConcreteProduct2 implements Product {
public operation(): string {
return '{Result of the ConcreteProduct2}';
}
}
/**
* The client code works with an instance of a concrete creator, albeit through
ELAACHAK Lotfi Page 27
* its base interface. As long as the client keeps working with the creator via
* the base interface, you can pass it any creator's subclass.
*/
function clientCode(creator: Creator) {
// ...
[Link]('Client: I\'m not aware of the creator\'s class, but it still works
.');
[Link]([Link]());
// ...
}
/**
* The Application picks a creator's type depending on the configuration or
* environment.
*/
[Link]('App: Launched with the ConcreteCreator1.');
clientCode(new ConcreteCreator1());
[Link]('');
[Link]('App: Launched with the ConcreteCreator2.');
clientCode(new ConcreteCreator2());
4.3 Abstract Factory
Le Factory abstraite est un modèle de conception créative qui vous permet de produire des
familles d’objets apparentés sans spécifier leurs classes concrètes.
ELAACHAK Lotfi Page 28
Abstract Factory
/**
* The Abstract Factory interface declares a set of methods that return
* different abstract products. These products are called a family and are
* related by a high-level theme or concept. Products of one family are usually
* able to collaborate among themselves. A family of products may have several
* variants, but the products of one variant are incompatible with products of
* another.
*/
interface AbstractFactory {
createProductA(): AbstractProductA;
createProductB(): AbstractProductB;
}
/**
* Concrete Factories produce a family of products that belong to a single
* variant. The factory guarantees that resulting products are compatible. Note
* that signatures of the Concrete Factory's methods return an abstract product,
* while inside the method a concrete product is instantiated.
*/
class ConcreteFactory1 implements AbstractFactory {
public createProductA(): AbstractProductA {
return new ConcreteProductA1();
}
public createProductB(): AbstractProductB {
return new ConcreteProductB1();
}
}
/**
* Each Concrete Factory has a corresponding product variant.
*/
class ConcreteFactory2 implements AbstractFactory {
public createProductA(): AbstractProductA {
return new ConcreteProductA2();
}
public createProductB(): AbstractProductB {
return new ConcreteProductB2();
}
}
/**
* Each distinct product of a product family should have a base interface. All
ELAACHAK Lotfi Page 29
* variants of the product must implement this interface.
*/
interface AbstractProductA {
usefulFunctionA(): string;
}
/**
* These Concrete Products are created by corresponding Concrete Factories.
*/
class ConcreteProductA1 implements AbstractProductA {
public usefulFunctionA(): string {
return 'The result of the product A1.';
}
}
class ConcreteProductA2 implements AbstractProductA {
public usefulFunctionA(): string {
return 'The result of the product A2.';
}
}
/**
* Here's the the base interface of another product. All products can interact
* with each other, but proper interaction is possible only between products of
* the same concrete variant.
*/
interface AbstractProductB {
/**
* Product B is able to do its own thing...
*/
usefulFunctionB(): string;
/**
* ...but it also can collaborate with the ProductA.
*
* The Abstract Factory makes sure that all products it creates are of the
* same variant and thus, compatible.
*/
anotherUsefulFunctionB(collaborator: AbstractProductA): string;
}
/**
* These Concrete Products are created by corresponding Concrete Factories.
*/
class ConcreteProductB1 implements AbstractProductB {
ELAACHAK Lotfi Page 30
public usefulFunctionB(): string {
return 'The result of the product B1.';
}
/**
* The variant, Product B1, is only able to work correctly with the variant,
* Product A1. Nevertheless, it accepts any instance of AbstractProductA as
* an argument.
*/
public anotherUsefulFunctionB(collaborator: AbstractProductA): string {
const result = [Link]();
return `The result of the B1 collaborating with the (${result})`;
}
}
class ConcreteProductB2 implements AbstractProductB {
public usefulFunctionB(): string {
return 'The result of the product B2.';
}
/**
* The variant, Product B2, is only able to work correctly with the variant,
* Product A2. Nevertheless, it accepts any instance of AbstractProductA as
* an argument.
*/
public anotherUsefulFunctionB(collaborator: AbstractProductA): string {
const result = [Link]();
return `The result of the B2 collaborating with the (${result})`;
}
}
/**
* The client code works with factories and products only through abstract
* types: AbstractFactory and AbstractProduct. This lets you pass any factory or
* product subclass to the client code without breaking it.
*/
function clientCode(factory: AbstractFactory) {
const productA = [Link]();
const productB = [Link]();
[Link]([Link]());
[Link]([Link](productA));
}
/**
ELAACHAK Lotfi Page 31
* The client code can work with any concrete factory class.
*/
[Link]('Client: Testing client code with the first factory type...');
clientCode(new ConcreteFactory1());
[Link]('');
[Link]('Client: Testing the same client code with the second factory type...')
;
clientCode(new ConcreteFactory2());
4.4 Prototype
Le prototype est un modèle de conception créative qui vous permet de copier des objets existants
sans rendre votre code dépendant de leurs classes.
Supposons que vous ayez un objet et que vous souhaitiez en créer une copie exacte. Comment
procéder ? Tout d’abord, vous devez créer un nouvel objet de la même classe. Ensuite, vous devez
parcourir tous les champs de l’objet original et copier leurs valeurs dans le nouvel objet.
Prototype
/**
* The example class that has cloning ability. We'll see how the values of field
* with different types will be cloned.
*/
class Prototype {
public primitive: any;
public component: object;
public circularReference: ComponentWithBackReference;
ELAACHAK Lotfi Page 32
public clone(): this {
const clone = [Link](this);
[Link] = [Link]([Link]);
// Cloning an object that has a nested object with backreference
// requires special treatment. After the cloning is completed, the
// nested object should point to the cloned object, instead of the
// original object. Spread operator can be handy for this case.
[Link] = {
...[Link],
prototype: { ...this },
};
return clone;
}
}
class ComponentWithBackReference {
public prototype;
constructor(prototype: Prototype) {
[Link] = prototype;
}
}
/**
* The client code.
*/
function clientCode() {
const p1 = new Prototype();
[Link] = 245;
[Link] = new Date();
[Link] = new ComponentWithBackReference(p1);
const p2 = [Link]();
if ([Link] === [Link]) {
[Link]('Primitive field values have been carried over to a clone. Yay!
');
} else {
[Link]('Primitive field values have not been copied. Booo!');
}
if ([Link] === [Link]) {
[Link]('Simple component has not been cloned. Booo!');
} else {
ELAACHAK Lotfi Page 33
[Link]('Simple component has been cloned. Yay!');
}
if ([Link] === [Link]) {
[Link]('Component with back reference has not been cloned. Booo!');
} else {
[Link]('Component with back reference has been cloned. Yay!');
}
if ([Link] === [Link]) {
[Link]('Component with back reference is linked to original object.
Booo!');
} else {
[Link]('Component with back reference is linked to the clone. Yay!');
}
}
clientCode();
4.5 Decorator
Le décorateur est un modèle de conception structurelle qui vous permet d’attacher de nouveaux
comportements aux objets en plaçant ces objets à l’intérieur d’objets enveloppants spéciaux qui
contiennent les comportements.
Imaginez que vous travaillez sur une bibliothèque de notification qui permet à d’autres pro-
grammes d’avertir leurs utilisateurs d’événements importants.
La version initiale de la bibliothèque était basée sur la classe Notificateur qui ne comportait que
quelques champs, un constructeur et une seule méthode d’envoi. Cette méthode pouvait accep-
ter un argument de message de la part d’un client et envoyer le message à une liste d’adresses
électroniques transmises au notificateur par l’intermédiaire de son constructeur. Une application
tierce faisant office de client était censée créer et configurer l’objet notificateur une fois pour
toutes, puis l’utiliser chaque fois qu’un événement important se produisait.
À un moment donné, vous vous rendez compte que les utilisateurs de la bibliothèque attendent
plus que de simples notifications par courrier électronique. Nombre d’entre eux aimeraient rece-
voir un SMS en cas de problèmes critiques. D’autres aimeraient être notifiés sur Facebook et,
bien sûr, les utilisateurs professionnels aimeraient recevoir des notifications sur Slack.
ELAACHAK Lotfi Page 34
Comment cela peut-il être difficile ? Vous avez étendu la classe Notifier et placé les méthodes de
notification supplémentaires dans de nouvelles sous-classes. Le client était alors censé instancier
la classe de notification souhaitée et l’utiliser pour toutes les notifications ultérieures.
Mais quelqu’un vous a alors raisonnablement demandé : « Pourquoi ne pouvez-vous pas utiliser
plusieurs types de notification à la fois ? Si votre maison est en feu, vous voudrez probablement
être informé par tous les canaux ».
Vous avez tenté de résoudre ce problème en créant des sous-classes spéciales combinant plusieurs
méthodes de notification au sein d’une même classe. Toutefois, il est rapidement apparu que cette
approche alourdirait considérablement le code, non seulement celui de la bibliothèque, mais aussi
celui du client.
Vous devez trouver un autre moyen de structurer les classes de notification afin que leur nombre
ne batte pas accidentellement un record Guinness.
ELAACHAK Lotfi Page 35
Decorator
4.6 Observer
Observer
/**
* The base Component interface defines operations that can be altered by
* decorators.
*/
interface Component {
operation(): string;
}
/**
* Concrete Components provide default implementations of the operations. There
* might be several variations of these classes.
*/
class ConcreteComponent implements Component {
public operation(): string {
return 'ConcreteComponent';
}
}
ELAACHAK Lotfi Page 36
/**
* The base Decorator class follows the same interface as the other components.
* The primary purpose of this class is to define the wrapping interface for all
* concrete decorators. The default implementation of the wrapping code might
* include a field for storing a wrapped component and the means to initialize
* it.
*/
class Decorator implements Component {
protected component: Component;
constructor(component: Component) {
[Link] = component;
}
/**
* The Decorator delegates all work to the wrapped component.
*/
public operation(): string {
return [Link]();
}
}
/**
* Concrete Decorators call the wrapped object and alter its result in some way.
*/
class ConcreteDecoratorA extends Decorator {
/**
* Decorators may call parent implementation of the operation, instead of
* calling the wrapped object directly. This approach simplifies extension
* of decorator classes.
*/
public operation(): string {
return `ConcreteDecoratorA(${[Link]()})`;
}
}
/**
* Decorators can execute their behavior either before or after the call to a
* wrapped object.
*/
class ConcreteDecoratorB extends Decorator {
public operation(): string {
return `ConcreteDecoratorB(${[Link]()})`;
}
}
ELAACHAK Lotfi Page 37
/**
* The client code works with all objects using the Component interface. This
* way it can stay independent of the concrete classes of components it works
* with.
*/
function clientCode(component: Component) {
// ...
[Link](`RESULT: ${[Link]()}`);
// ...
}
/**
* This way the client code can support both simple components...
*/
const simple = new ConcreteComponent();
[Link]('Client: I\'ve got a simple component:');
clientCode(simple);
[Link]('');
/**
* ...as well as decorated ones.
*
* Note how decorators can wrap not only simple components but the other
* decorators as well.
*/
const decorator1 = new ConcreteDecoratorA(simple);
const decorator2 = new ConcreteDecoratorB(decorator1);
[Link]('Client: Now I\'ve got a decorated component:');
clientCode(decorator2);
4.7 Dependency Injection
L’injection de dépendances (DI) en TypeScript est un modèle de conception dans lequel les objets
reçoivent leurs dépendances au lieu de les créer en interne. Elle permet d’améliorer la modularité,
les tests et le découplage des composants. Voici une vue d’ensemble et des exemples pour mettre
en œuvre l’injection de dépendances en TypeScript :
DI
// Service to inject
class Service {
public doSomething(): void {
[Link]("Service is doing something.");
}
}
ELAACHAK Lotfi Page 38
// Dependent class
class Consumer {
private service: Service;
constructor(service: Service) {
[Link] = service; // Dependency is injected here
}
public useService(): void {
[Link]();
}
}
// Usage
const service = new Service(); // Instantiate the dependency
const consumer = new Consumer(service); // Inject dependency
[Link]();
DI
// Define an interface for the service
interface IService {
doSomething(): void;
}
// Concrete implementation
class ConcreteService implements IService {
public doSomething(): void {
[Link]("ConcreteService is doing something.");
}
}
// Consumer depends on IService, not on a specific implementation
class Consumer {
private service: IService;
constructor(service: IService) {
[Link] = service;
}
public useService(): void {
[Link]();
}
}
ELAACHAK Lotfi Page 39
// Usage
const service = new ConcreteService();
const consumer = new Consumer(service);
[Link]();
Principaux avantages de l’injection de dépendance
1. Découplage : Les classes n’ont pas besoin de savoir comment les dépendances sont créées.
2. Testabilité : Injection facile de mocks ou de stubs pour les tests.
3. Évolutivité : Utile pour les grandes applications où la gestion manuelle des dépendances
est difficile.
ELAACHAK Lotfi Page 40