🔝 Retour au Sommaire
L'organisation du code source et la modularité sont essentielles pour créer des applications maintenables, évolutives et compréhensibles. Un code bien organisé est comme une maison bien rangée : on trouve facilement ce qu'on cherche et il est agréable d'y travailler.
Imaginez deux bibliothèques :
Bibliothèque désorganisée :
- Les livres sont empilés au hasard
- Pas de classification
- Impossible de trouver un livre spécifique
- Perte de temps constante
Bibliothèque organisée :
- Les livres sont classés par catégories
- Chaque section a sa place
- Étiquettes claires
- Facile de trouver et de ranger
Votre code doit être comme la bibliothèque organisée !
- Lisibilité : facile à comprendre
- Maintenabilité : facile à modifier et corriger
- Réutilisabilité : facile de réutiliser des parties du code
- Collaboration : facile de travailler en équipe
- Évolutivité : facile d'ajouter de nouvelles fonctionnalités
- Tests : facile de tester chaque partie séparément
- Performance : compilation plus rapide avec une bonne organisation
Une unité (unit) est un fichier source Delphi qui contient du code réutilisable. C'est le bloc de construction fondamental pour organiser votre code en Delphi.
Chaque unité :
- Est un fichier
.pas - A un nom unique
- Contient des types, des classes, des fonctions, des procédures, des constantes
- Peut être réutilisée dans différents projets
unit NomDeLUnite;
// Section INTERFACE : ce qui est visible de l'extérieur
interface
uses
System.SysUtils, System.Classes; // Unités utilisées
type
// Déclaration des types, classes, etc.
TMaClasse = class
public
procedure FaireQuelqueChose;
end;
// Déclaration des fonctions et procédures publiques
function CalculerTotal(A, B: Integer): Integer;
// Section IMPLEMENTATION : le code interne
implementation
procedure TMaClasse.FaireQuelqueChose;
begin
// Implémentation
end;
function CalculerTotal(A, B: Integer): Integer;
begin
Result := A + B;
end;
// Section INITIALIZATION (optionnelle)
initialization
// Code exécuté au démarrage de l'application
// Section FINALIZATION (optionnelle)
finalization
// Code exécuté à la fermeture de l'application
end.La section interface définit ce qui est visible et accessible depuis l'extérieur de l'unité.
interface
uses
System.SysUtils; // Unités nécessaires pour l'interface
type
TCalculatrice = class
public
function Additionner(A, B: Integer): Integer;
end;
// Cette fonction est accessible de l'extérieur
function CalculerMoyenne(Valeurs: array of Double): Double;Règle : Tout ce qui est dans interface est public et utilisable par d'autres unités.
La section implementation contient le code réel des méthodes et peut contenir des éléments privés à l'unité.
implementation
uses
System.Math; // Unité nécessaire uniquement pour l'implémentation
// Fonction privée, invisible de l'extérieur
function FonctionInterne: Integer;
begin
Result := 42;
end;
function TCalculatrice.Additionner(A, B: Integer): Integer;
begin
Result := A + B;
end;
function CalculerMoyenne(Valeurs: array of Double): Double;
var
I: Integer;
Somme: Double;
begin
Somme := 0;
for I := Low(Valeurs) to High(Valeurs) do
Somme := Somme + Valeurs[I];
Result := Somme / Length(Valeurs);
end;Règle : Ce qui est déclaré uniquement dans implementation est privé à l'unité.
var
CompteurGlobal: Integer;
initialization
// Exécuté au démarrage de l'application
CompteurGlobal := 0;
WriteLn('Unité initialisée');
finalization
// Exécuté à la fermeture de l'application
WriteLn('Unité finalisée');
// Libérer les ressources globalesLa clause uses indique quelles autres unités sont nécessaires pour votre code.
unit MonUnite;
interface
uses
System.SysUtils, // Pour les fonctions de chaînes
System.Classes, // Pour TStringList, etc.
Vcl.Dialogs; // Pour ShowMessage
implementation
uses
System.Math; // Uniquement pour l'implémentationinterface
uses
System.SysUtils; // Nécessaire pour l'interface publique
// Les unités ici sont "propagées" aux utilisateurs de cette unité
implementation
uses
System.Math; // Nécessaire uniquement pour l'implémentation
// Les unités ici restent privées à l'unitéRègle d'or : Mettez dans interface/uses uniquement ce qui est strictement nécessaire pour les déclarations publiques. Le reste va dans implementation/uses.
Problème : Deux unités qui se référencent mutuellement.
// Unit1.pas
unit Unit1;
interface
uses Unit2; // Unit1 utilise Unit2
// Unit2.pas
unit Unit2;
interface
uses Unit1; // Unit2 utilise Unit1 - ERREUR ! Solution : Déplacer l'une des références dans la section implementation.
// Unit1.pas
unit Unit1;
interface
uses Unit2;
// Unit2.pas
unit Unit2;
interface
// Pas de uses Unit1 ici
implementation
uses Unit1; // Référence déplacée ici Chaque unité doit avoir une responsabilité unique et claire.
// ❌ Mauvais - une seule unité pour tout
unit ToutMonCode;
// Classes de modèles
// Classes de connexion DB
// Classes d'interface
// Fonctions utilitaires
// Tout mélangé !
end.
// ✅ Bon - unités séparées par responsabilité
unit App.Modeles; // Classes de données
unit App.Database; // Accès aux données
unit App.Interface; // Composants d'interface
unit App.Utilitaires; // Fonctions utilitaires Mon Application de Gestion
├── Modèles (données)
│ ├── App.Modeles.Client.pas
│ ├── App.Modeles.Commande.pas
│ └── App.Modeles.Produit.pas
│
├── Accès aux données
│ ├── App.Data.Connexion.pas
│ ├── App.Data.Client.pas
│ └── App.Data.Commande.pas
│
├── Logique métier
│ ├── App.Business.Vente.pas
│ └── App.Business.Stock.pas
│
└── Interface utilisateur
├── App.UI.MainForm.pas
├── App.UI.ClientForm.pas
└── App.UI.CommandeForm.pas
MonProjet/
├── Source/
│ ├── Modeles/
│ │ ├── Client.pas
│ │ ├── Commande.pas
│ │ └── Produit.pas
│ │
│ ├── Data/
│ │ ├── Connexion.pas
│ │ ├── ClientDAO.pas
│ │ └── CommandeDAO.pas
│ │
│ ├── Business/
│ │ ├── GestionVentes.pas
│ │ └── GestionStock.pas
│ │
│ ├── UI/
│ │ ├── MainForm.pas
│ │ ├── MainForm.dfm
│ │ ├── ClientForm.pas
│ │ └── ClientForm.dfm
│ │
│ └── Utils/
│ ├── StringUtils.pas
│ └── DateUtils.pas
│
├── Tests/
│ ├── TestClient.pas
│ └── TestCommande.pas
│
├── Resources/
│ ├── Images/
│ └── Icons/
│
└── Documentation/
└── README.md
- Navigation facile : on sait où chercher
- Compilation par modules : changements localisés
- Travail en équipe : moins de conflits
- Réutilisation : modules indépendants
- Tests : chaque module testable séparément
Un namespace (espace de noms) est un préfixe qui permet d'organiser et de distinguer les unités.
// Namespaces standards de Delphi
System.SysUtils // Namespace System
System.Classes
System.Generics.Collections
Vcl.Forms // Namespace Vcl (Visual Component Library)
Vcl.Dialogs
Vcl.Controls
FMX.Forms // Namespace FMX (FireMonkey)
FMX.Types // MonApp.Modeles.Client.pas
unit MonApp.Modeles.Client;
interface
type
TClient = class
// ...
end;
implementation
end.- Organisation hiérarchique : structure claire
- Évite les conflits de noms : deux unités peuvent avoir le même nom dans des namespaces différents
- Indication de la fonction : on comprend directement le rôle de l'unité
// ✅ Bon - structure claire
CompanyName.ProjectName.Category.Feature
// Exemples concrets
MonEntreprise.GestionStock.Modeles.Produit
MonEntreprise.GestionStock.Data.ProduitDAO
MonEntreprise.GestionStock.UI.ProduitForm
// ✅ Bon - simplifié
App.Models.Customer
App.Data.CustomerDAO
App.UI.CustomerForm La modularité consiste à diviser votre application en modules indépendants et réutilisables.
// App.Utils.Logger.pas
unit App.Utils.Logger;
interface
type
TLogLevel = (llDebug, llInfo, llWarning, llError);
TLogger = class
private
class var FInstance: TLogger;
FLogFile: string;
public
class function Instance: TLogger;
procedure Log(const Message: string; Level: TLogLevel = llInfo);
procedure Debug(const Message: string);
procedure Error(const Message: string);
end;
implementation
uses
System.SysUtils, System.IOUtils;
class function TLogger.Instance: TLogger;
begin
if FInstance = nil then
FInstance := TLogger.Create;
Result := FInstance;
end;
procedure TLogger.Log(const Message: string; Level: TLogLevel);
var
LogMessage: string;
begin
LogMessage := Format('[%s] [%s] %s',
[DateTimeToStr(Now), GetEnumName(TypeInfo(TLogLevel), Ord(Level)), Message]);
// Écrire dans le fichier...
end;
procedure TLogger.Debug(const Message: string);
begin
Log(Message, llDebug);
end;
procedure TLogger.Error(const Message: string);
begin
Log(Message, llError);
end;
end.Ce module peut être réutilisé dans n'importe quel projet !
Regroupez les fonctionnalités communes dans des unités dédiées :
// App.Utils.StringHelper.pas
unit App.Utils.StringHelper;
interface
type
TStringHelper = class
public
class function EstEmail(const Email: string): Boolean;
class function EstTelephone(const Tel: string): Boolean;
class function CapitaliserMots(const Texte: string): string;
class function RemplacerAccents(const Texte: string): string;
end;
implementation
class function TStringHelper.EstEmail(const Email: string): Boolean;
begin
// Validation d'email
Result := Pos('@', Email) > 0;
end;
class function TStringHelper.EstTelephone(const Tel: string): Boolean;
begin
// Validation de téléphone
Result := Length(Tel) >= 10;
end;
class function TStringHelper.CapitaliserMots(const Texte: string): string;
begin
// Mettre une majuscule au début de chaque mot
Result := Texte; // Implémentation simplifiée
end;
class function TStringHelper.RemplacerAccents(const Texte: string): string;
begin
// Remplacer les accents
Result := StringReplace(Texte, 'é', 'e', [rfReplaceAll]);
// etc.
end;
end.Organisez votre application en couches distinctes :
┌─────────────────────────────────┐
│ Couche Présentation (UI) │ Formulaires, composants visuels
├─────────────────────────────────┤
│ Couche Logique Métier │ Règles, calculs, validations
├─────────────────────────────────┤
│ Couche Accès aux Données │ Connexion DB, requêtes
├─────────────────────────────────┤
│ Couche Données (Modèles) │ Structures de données
└─────────────────────────────────┘
// ===== COUCHE MODÈLE =====
// App.Models.Client.pas
unit App.Models.Client;
interface
type
TClient = class
private
FID: Integer;
FNom: string;
FEmail: string;
public
property ID: Integer read FID write FID;
property Nom: string read FNom write FNom;
property Email: string read FEmail write FEmail;
end;
implementation
end.
// ===== COUCHE DONNÉES =====
// App.Data.ClientDAO.pas
unit App.Data.ClientDAO;
interface
uses
App.Models.Client, System.Generics.Collections;
type
TClientDAO = class
public
function ObtenirTous: TList<TClient>;
function ObtenirParID(ID: Integer): TClient;
procedure Enregistrer(Client: TClient);
procedure Supprimer(ID: Integer);
end;
implementation
uses
FireDAC.Comp.Client; // Accès aux données
function TClientDAO.ObtenirTous: TList<TClient>;
begin
Result := TList<TClient>.Create;
// Code pour charger depuis la base de données
end;
function TClientDAO.ObtenirParID(ID: Integer): TClient;
begin
Result := TClient.Create;
// Code pour charger un client spécifique
end;
procedure TClientDAO.Enregistrer(Client: TClient);
begin
// Code pour sauvegarder dans la base de données
end;
procedure TClientDAO.Supprimer(ID: Integer);
begin
// Code pour supprimer de la base de données
end;
end.
// ===== COUCHE LOGIQUE MÉTIER =====
// App.Business.ClientService.pas
unit App.Business.ClientService;
interface
uses
App.Models.Client, App.Data.ClientDAO;
type
TClientService = class
private
FDAO: TClientDAO;
public
constructor Create;
destructor Destroy; override;
function ValiderEmail(const Email: string): Boolean;
function CreerNouveauClient(const Nom, Email: string): TClient;
procedure EnregistrerClient(Client: TClient);
end;
implementation
uses
System.SysUtils, System.RegularExpressions;
constructor TClientService.Create;
begin
inherited Create;
FDAO := TClientDAO.Create;
end;
destructor TClientService.Destroy;
begin
FDAO.Free;
inherited Destroy;
end;
function TClientService.ValiderEmail(const Email: string): Boolean;
begin
// Règle métier : validation d'email
Result := TRegEx.IsMatch(Email, '^[\w\.-]+@[\w\.-]+\.\w+$');
end;
function TClientService.CreerNouveauClient(const Nom, Email: string): TClient;
begin
if not ValiderEmail(Email) then
raise Exception.Create('Email invalide');
Result := TClient.Create;
Result.Nom := Nom;
Result.Email := Email;
end;
procedure TClientService.EnregistrerClient(Client: TClient);
begin
// Règles métier avant sauvegarde
if Trim(Client.Nom) = '' then
raise Exception.Create('Le nom est obligatoire');
if not ValiderEmail(Client.Email) then
raise Exception.Create('Email invalide');
FDAO.Enregistrer(Client);
end;
end.
// ===== COUCHE PRÉSENTATION =====
// App.UI.ClientForm.pas
unit App.UI.ClientForm;
interface
uses
Vcl.Forms, Vcl.StdCtrls, App.Business.ClientService, App.Models.Client;
type
TFormClient = class(TForm)
EditNom: TEdit;
EditEmail: TEdit;
ButtonEnregistrer: TButton;
procedure ButtonEnregistrerClick(Sender: TObject);
private
FService: TClientService;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
implementation
uses
Vcl.Dialogs, System.SysUtils;
constructor TFormClient.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FService := TClientService.Create;
end;
destructor TFormClient.Destroy;
begin
FService.Free;
inherited Destroy;
end;
procedure TFormClient.ButtonEnregistrerClick(Sender: TObject);
var
Client: TClient;
begin
try
Client := FService.CreerNouveauClient(EditNom.Text, EditEmail.Text);
try
FService.EnregistrerClient(Client);
ShowMessage('Client enregistré avec succès');
finally
Client.Free;
end;
except
on E: Exception do
ShowMessage('Erreur : ' + E.Message);
end;
end;
end.- Testabilité : chaque couche peut être testée indépendamment
- Maintenance : changements localisés
- Réutilisabilité : les couches basses peuvent être réutilisées
- Clarté : responsabilités bien définies
- Évolutivité : facile d'ajouter des fonctionnalités
// ✅ Bon - descriptif et clair
App.Models.Customer.pas
App.Data.CustomerRepository.pas
App.Utils.StringHelper.pas
// ❌ Mauvais - trop vague
Unit1.pas
Utils.pas
Stuff.pas [Namespace].[Catégorie].[Fonctionnalité].pas
Exemples :
MonApp.Models.Product.pas
MonApp.Data.ProductDAO.pas
MonApp.Business.SalesService.pas
MonApp.UI.MainForm.pas
MonApp.Utils.Logger.pas
| Type | Préfixe | Exemple |
|---|---|---|
| Classe | T | TClient, TCommande |
| Interface | I | IRepository, ILogger |
| Énumération | T | TStatus, TLogLevel |
| Record | T | TPoint, TRectangle |
| Formulaire | TForm | TFormClient, TFormMain |
Delphi supporte les commentaires de documentation XML :
/// <summary>
/// Calcule la moyenne d'un tableau de valeurs
/// </summary>
/// <param name="Valeurs">Tableau de valeurs à moyenner</param>
/// <returns>La moyenne des valeurs</returns>
/// <exception cref="Exception">Si le tableau est vide</exception>
function CalculerMoyenne(Valeurs: array of Double): Double;{******************************************************************************}
{ }
{ Unité : App.Utils.StringHelper }
{ Description : Fonctions utilitaires pour la manipulation de chaînes }
{ Auteur : Votre Nom }
{ Date : 01/01/2025 }
{ Version : 1.0 }
{ }
{******************************************************************************}
unit App.Utils.StringHelper;// ✅ Bon - explique le POURQUOI
// On utilise un timeout de 30s car certaines requêtes sont lentes
FDConnection.Timeout := 30000;
// ❌ Mauvais - dit ce que fait le code (déjà évident)
// Définit le timeout à 30000
FDConnection.Timeout := 30000;// ❌ Mauvais - trop de dépendances
unit MonUnite;
interface
uses
System.SysUtils, System.Classes, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
Vcl.ExtCtrls, Vcl.Grids, Data.DB, FireDAC.Comp.Client, FireDAC.Stan.Param,
System.Generics.Collections, System.JSON, System.RegularExpressions;
// ✅ Bon - dépendances minimales
unit MonUnite;
interface
uses
System.SysUtils, System.Classes; // Seulement ce qui est nécessaire
implementation
uses
Vcl.Dialogs; // Utilisé uniquement dans l'implémentationUtilisez des interfaces pour découpler les modules :
// ❌ Mauvais - couplage fort
type
TClientService = class
private
FMySQL: TMySQLConnection; // Dépend d'une implémentation concrète
end;
// ✅ Bon - couplage faible
type
IDatabase = interface
procedure Connect;
procedure Execute(const SQL: string);
end;
TClientService = class
private
FDatabase: IDatabase; // Dépend d'une abstraction
end;// ✅ Bon
// Client.pas - contient uniquement TClient
// Commande.pas - contient uniquement TCommande
// ⚠️ Acceptable si classes très liées
// Transaction.pas - contient TTransaction et TTransactionItem// Classes qui changent ensemble → même unité
unit App.Models.Vente;
type
TVente = class
// ...
end;
TLigneVente = class // Très lié à TVente
// ...
end;// IRepository.pas - interface uniquement
unit App.Interfaces.IRepository;
interface
type
IRepository<T> = interface
procedure Save(Entity: T);
function GetById(ID: Integer): T;
end;
implementation
end.
// ClientRepository.pas - implémentation
unit App.Data.ClientRepository;
interface
uses
App.Interfaces.IRepository, App.Models.Client;
type
TClientRepository = class(TInterfacedObject, IRepository<TClient>)
public
procedure Save(Entity: TClient);
function GetById(ID: Integer): TClient;
end;
implementation
// ...
end.// Config.inc - constantes de configuration
const
APP_VERSION = '1.0.0';
MAX_CONNECTIONS = 100;
// Dans votre unité
unit MonUnite;
interface
{$I Config.inc} // Inclure le fichier
implementation
end. Attention : Ne pas abuser des includes, préférez les unités normales.
// ✅ Bon - facile de voir ce qui est utilisé
uses
Data.DB,
FireDAC.Comp.Client,
System.Classes,
System.Generics.Collections,
System.SysUtils,
Vcl.Dialogs;
// ❌ Mauvais - désorganisé
uses
Vcl.Dialogs, System.SysUtils, FireDAC.Comp.Client, Data.DB,
System.Classes;GestionCommerciale/
├── Source/
│ ├── Core/
│ │ ├── App.Core.Interfaces.pas (Interfaces communes)
│ │ ├── App.Core.Types.pas (Types communs)
│ │ └── App.Core.Constants.pas (Constantes)
│ │
│ ├── Models/
│ │ ├── App.Models.Client.pas
│ │ ├── App.Models.Produit.pas
│ │ ├── App.Models.Commande.pas
│ │ └── App.Models.Facture.pas
│ │
│ ├── Data/
│ │ ├── App.Data.Connection.pas
│ │ ├── App.Data.ClientDAO.pas
│ │ ├── App.Data.ProduitDAO.pas
│ │ └── App.Data.CommandeDAO.pas
│ │
│ ├── Business/
│ │ ├── App.Business.ClientService.pas
│ │ ├── App.Business.VenteService.pas
│ │ └── App.Business.StockService.pas
│ │
│ ├── UI/
│ │ ├── Forms/
│ │ │ ├── App.UI.MainForm.pas/.dfm
│ │ │ ├── App.UI.ClientForm.pas/.dfm
│ │ │ └── App.UI.CommandeForm.pas/.dfm
│ │ │
│ │ └── Components/
│ │ ├── App.UI.ClientGrid.pas
│ │ └── App.UI.SearchPanel.pas
│ │
│ └── Utils/
│ ├── App.Utils.Logger.pas
│ ├── App.Utils.StringHelper.pas
│ └── App.Utils.Validator.pas
│
├── Tests/
│ ├── Tests.Models.pas
│ ├── Tests.Data.pas
│ └── Tests.Business.pas
│
└── Resources/
├── Images/
├── SQL/
└── Config/
- Ctrl + Clic : aller à la définition
- Ctrl + Shift + Flèches : naviguer entre déclaration/implémentation
- Ctrl + G : aller à la ligne
- Ctrl + Q + Q : marquer/revenir à une position
- Renommer (Ctrl + Shift + E) : renommer un symbole partout
- Extraire méthode : créer une méthode depuis du code sélectionné
- Déplacer : déplacer des méthodes entre classes
Organisez vos fichiers en groupes logiques dans le gestionnaire de projet.
-
Unités : blocs de construction fondamentaux
- Section
interface: ce qui est public - Section
implementation: le code interne - Clause
uses: gérer les dépendances
- Section
-
Organisation par fonctionnalité
- Une responsabilité par unité
- Séparation en couches (Modèles, Data, Business, UI)
- Structure de dossiers claire
-
Namespaces : organiser hiérarchiquement
- Format :
Company.Project.Category.Feature - Évite les conflits de noms
- Format :
-
Modularité
- Modules indépendants et réutilisables
- Minimiser les dépendances
- Utiliser l'inversion de dépendance
-
Bonnes pratiques
- Nommage cohérent et descriptif
- Documentation du code
- Une classe par unité (généralement)
- Uses alphabétiques
- Séparer interface et implémentation
-
Avantages
- Code lisible et maintenable
- Facile à tester
- Réutilisable
- Évolutif
- Travail en équipe facilité
Un code bien organisé est un investissement qui se rentabilise rapidement. Prenez le temps d'organiser correctement dès le début, vous gagnerez beaucoup de temps par la suite !
⏭️ Nouveautés de la syntaxe Object Pascal (dernières versions)