Classes et Interfaces TypeScript
Conventions de nommage
Classes
- PascalCase pour les noms de classes
- Les noms doivent être descriptifs et refléter la responsabilité de la classe
class UserService {
// Propriétés et méthodes
}
class HttpClient {
// Propriétés et méthodes
}
class DatabaseConnection {
// Propriétés et méthodes
}
Interfaces
- PascalCase pour les noms d'interfaces
- Préférer les interfaces aux alias de type pour définir la forme des objets
- Éviter le préfixe "I" devant les noms d'interfaces
interface User {
name: string;
id: number;
email?: string;
}
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface DatabaseConfig {
host: string;
port: number;
username: string;
password: string;
}
Déclaration des Classes
Propriétés de classe
- Utiliser les modificateurs de visibilité appropriés (
public,private,protected) - Préférer
readonlypour les propriétés immutables - Initialiser les propriétés lors de leur déclaration quand c'est possible
class Employee {
// Propriétés publiques
public readonly id: number;
public name: string;
// Propriétés privées
private _salary: number = 0;
// Propriétés protégées
protected department: string;
constructor(id: number, name: string, department: string) {
this.id = id;
this.name = name;
this.department = department;
}
}
Propriétés de paramètre (Parameter Properties)
- Utiliser les propriétés de paramètre pour réduire le code répétitif
- Préfixer les paramètres du constructeur avec les modificateurs de visibilité
class Student {
constructor(
public readonly studentId: string,
public firstName: string,
public lastName: string,
private _grade: number
) {
// Le constructeur est automatiquement simplifié
}
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
Implémentation d'Interfaces
Classes implémentant des interfaces
- Utiliser
implementspour s'assurer qu'une classe respecte un contrat - Une classe peut implémenter plusieurs interfaces
interface Serializable {
serialize(): string;
}
interface Comparable<T> {
compareTo(other: T): number;
}
class Product implements Serializable, Comparable<Product> {
constructor(
public readonly id: string,
public name: string,
public price: number
) {}
serialize(): string {
return JSON.stringify({
id: this.id,
name: this.name,
price: this.price
});
}
compareTo(other: Product): number {
return this.price - other.price;
}
}
Extension d'Interfaces
Héritage d'interface
- Utiliser
extendspour créer des interfaces spécialisées - Une interface peut étendre plusieurs interfaces
interface Animal {
name: string;
age: number;
}
interface Mammal extends Animal {
furColor: string;
isWarmBlooded: boolean;
}
interface Pet extends Animal {
owner: string;
isVaccinated: boolean;
}
// Interface combinant plusieurs héritages
interface Dog extends Mammal, Pet {
breed: string;
bark(): void;
}
Membres Statiques
Propriétés et méthodes statiques
- Utiliser
staticpour les membres partagés entre toutes les instances - Éviter les noms conflictuels avec les propriétés Function (
name,length)
class MathUtils {
static readonly PI = 3.14159;
static readonly E = 2.71828;
static calculateArea(radius: number): number {
return MathUtils.PI * radius * radius;
}
static max(a: number, b: number): number {
return a > b ? a : b;
}
}
// Utilisation
const area = MathUtils.calculateArea(5);
const maximum = MathUtils.max(10, 20);
Accesseurs (Getters/Setters)
Propriétés calculées et validation
- Utiliser les getters pour les propriétés calculées
- Utiliser les setters pour la validation des données
- Les getters doivent être des fonctions pures (sans effets de bord)
class Rectangle {
private _width: number = 0;
private _height: number = 0;
get width(): number {
return this._width;
}
set width(value: number) {
if (value < 0) {
throw new Error('Width cannot be negative');
}
this._width = value;
}
get height(): number {
return this._height;
}
set height(value: number) {
if (value < 0) {
throw new Error('Height cannot be negative');
}
this._height = value;
}
// Propriété calculée en lecture seule
get area(): number {
return this._width * this._height;
}
get perimeter(): number {
return 2 * (this._width + this._height);
}
}
Héritage de Classes
Extension de classes
- Utiliser
extendspour créer des hiérarchies de classes - Appeler
super()dans le constructeur des classes dérivées - Utiliser
overridepour clarifier les méthodes redéfinies
abstract class Vehicle {
protected constructor(
public readonly make: string,
public readonly model: string,
protected _speed: number = 0
) {}
abstract startEngine(): void;
abstract stopEngine(): void;
accelerate(increment: number): void {
this._speed += increment;
}
get currentSpeed(): number {
return this._speed;
}
}
class Car extends Vehicle {
constructor(
make: string,
model: string,
private _fuelLevel: number = 100
) {
super(make, model);
}
override startEngine(): void {
if (this._fuelLevel > 0) {
console.log(`${this.make} ${this.model} engine started`);
} else {
throw new Error('Cannot start: no fuel');
}
}
override stopEngine(): void {
this._speed = 0;
console.log(`${this.make} ${this.model} engine stopped`);
}
refuel(amount: number): void {
this._fuelLevel = Math.min(100, this._fuelLevel + amount);
}
}
Bonnes Pratiques
1. Préférer la composition à l'héritage
// Plutôt que l'héritage multiple (non supporté)
class Logger {
log(message: string): void {
console.log(`[${new Date().toISOString()}] ${message}`);
}
}
class UserService {
constructor(private logger: Logger) {}
createUser(userData: any): void {
// Logique de création
this.logger.log('User created successfully');
}
}
2. Utiliser des interfaces pour les contrats
interface Repository<T> {
findById(id: string): Promise<T | null>;
save(entity: T): Promise<T>;
delete(id: string): Promise<void>;
}
class UserRepository implements Repository<User> {
async findById(id: string): Promise<User | null> {
// Implémentation
return null;
}
async save(user: User): Promise<User> {
// Implémentation
return user;
}
async delete(id: string): Promise<void> {
// Implémentation
}
}
3. Éviter les classes vides
// ❌ Éviter
class Empty {}
// ✅ Préférer des interfaces ou types
interface Point {
x: number;
y: number;
}
type EventHandler = (event: Event) => void;
4. Utiliser des types d'union pour la flexibilité
interface LoadingState {
status: 'loading';
}
interface SuccessState {
status: 'success';
data: any;
}
interface ErrorState {
status: 'error';
error: string;
}
type ApiState = LoadingState | SuccessState | ErrorState;
class ApiClient {
private _state: ApiState = { status: 'loading' };
get state(): ApiState {
return this._state;
}
private setState(newState: ApiState): void {
this._state = newState;
}
}