novembre
2008
2 – Persistance et Visitor pattern
tiOPF utilise le pattern Visitor (ou « modèle de conception Visiteur » pour les irréductibles gaulois). Nous allons donc appliquer ce pattern au modèle-objet décrit dans l’article précédent.
Afin de rendre l’utilisation du framework de persistance plus souple, nous allons appliquer le pattern à l’ancêtre commun des classes du BOM (« Business Object Model » ou « modèle-objet du domaine de l’application ») : TBizObj.
Toujours dans un souci d’extensibilité, nous créons aussi un ancêtre commun à tous les visiteurs des classes du BOM : TBizObjVisitor.
Ces classes font partie du mécanisme de persistance des objets et seront regroupées dans l’unité uOPFRoot.
public
procedure VisitBizObj(Instance: TBizObj); virtual;
end;
TBizObj = class(TObject)
public
procedure AcceptBizObjVisitor(Visitor: TBizObjVisitor); virtual;
end;
implementation
{
******************************** TBizObjVisitor ********************************
}
procedure TBizObjVisitor.VisitBizObj(Instance: TBizObj);
begin
end;
{
*********************************** TBizObj ************************************
}
procedure TBizObj.AcceptBizObjVisitor(Visitor: TBizObjVisitor);
begin
Visitor.VisitBizObj(Self);
end;
Les méthodes sont virtuelles afin de pouvoir être implémentées différemment dans les classes
descendantes.
2.1 – Persistance par fichier-texte
Notre premier objectif est de sauvegarder les données dans un fichier-texte. Nous utilisons pour cela la méthode SaveToFile de la classe TStringList. Il nous faut donc générer une StringList à partir de la liste des objets TPerson contenue dans TPeople. C’est un Visitor qui est chargé de cette opération : il parcourt la liste des personnes et convertit chaque objet de la classe TPerson en une ligne de texte qui sera ajoutée au fur et à mesure dans la StringList. Cette dernière sera finalement sauvegardée dans un fichier-texte avec la méthode SaveToFile.
Une ligne de texte peut être une concaténation de sous-chaînes de longueur fixe, chaque sous-chaîne correspondant à un attribut (ou propriété) de TPeople. La StringList ainsi obtenue est sauvegardée dans un fichier avec l’extension txt.
Une ligne de texte peut aussi être composée de champs séparés par un délimiteur, par exemple le point-virgule. Le fichier obtenu aura l’extension csv.
Il nous faut donc construire deux visiteurs, un pour chaque format de fichier. Nous les nommons TFixedTextFileSave et TCsvTextFileSave. Dans un souci de factorisation du code, TFixedTextFileSave partagera un ancêtre commun avec TFixedTextFileRead : TFixedTextFile. De même TCsvTextFileSave aura un ancêtre TCsvTextFile en commun avec TCsvTextFileRead. TFixedTextFile et TCsvTextFile ont pour ancêtre TTextFile qui descend lui-même de TBizObjVisitor pour assurer le polymorphisme.
2.1.1 – Fichier de texte délimité
Commençons par la sauvegarde dans un fichier-texte avec des délimiteurs point-virgule. La hiérarchie des classes utilisées apparaît ci-dessous. Ces classes sont regroupées dans une unité spécifique aux fichiers-texte uOPFTextFiles.
private
FFileName: TFileName;
FStringList: TStringList;
public
constructor Create(aFileName: TFileName); override;
destructor Destroy; override;
end;
TCsvTextFile = class(TTextFile)
public
constructor Create(aFileName: TFileName); overload; override;
destructor Destroy; override;
end;
TCsvTextFileSave = class(TCsvTextFile)
public
destructor Destroy; override;
procedure VisitBizObj(Visited: TBizObj); override;
end;
implementation
uses
uBOMRep;
{
********************************** TTextFile ***********************************
}
constructor TTextFile.Create(aFileName: TFileName);
begin
inherited Create(aFileName);
FFileName := aFileName;
FStringList := TStringList.Create;
end;
destructor TTextFile.Destroy;
begin
FStringList.SaveTofile(FFileName);
FStringList.Free;
inherited Destroy;
end;
{
********************************* TCsvTextFile *********************************
}
constructor TCsvTextFile.Create(aFileName: TFileName);
begin
// aFileName must have .txt extension
if ExtractFileExt(aFileName) <> '.csv' then
ChangeFileExt(aFileName, '.csv');
inherited Create(aFileName);
end;
destructor TCsvTextFile.Destroy;
begin
inherited Destroy;
end;
{
******************************* TCsvTextFileSave *******************************
}
destructor TCsvTextFileSave.Destroy;
begin
inherited Destroy;
end;
procedure TCsvTextFileSave.VisitBizObj(Visited: TBizObj);
var
i: Integer;
begin
if not(Visited is TPeople) then
Exit;
for i := 0 to TPeople(Visited).List.Count - 1 do begin
FStringList.Add(
TPerson(TPeople(Visited).List.Items[i]).LastName + ';' +
TPerson(TPeople(Visited).List.Items[i]).FirstName + ';' +
DateToStr(TPerson(TPeople(Visited).List.Items[i]).BirthDate)
);
end;
end;
La méthode VisitBizObj de TCsvTextFileSave ci-dessus contient le mécanisme de conversion des objets de la liste visitée en lignes de texte délimité par des points-virgules.
Quand le visiteur est détruit, l’appel des destructeurs des ancêtres successifs finit par lancer la méthode SaveTofile de la StringList dans le destructeur TTextFile.Destroy.
La sauvegarde est lancée dans la fiche principale TFormRepert comme suit:
var
CsvSave: TCsvTextFileSave;
begin
CsvSave := TCsvTextFileSave.Create(FN);
try
if MessageDlg('Sauvegarder ' + FN + ' ?',
mtConfirmation, [mbYes, mbNo], 0) = mrYes then
People.AcceptBizObjVisitor(CsvSave);
finally
CsvSave.Free;
end;
end;
La procédure commence par créer une instance du visiteur TCsvTextFileSave en lui passant en paramètre le nom et le chemin du fichier de destination. Un message de confirmation est alors affiché. Si la réponse est affirmative, l’objet People appelle le visiteur avec la méthode AcceptBizObjVisitor de son ancêtre TBizObj.
AcceptBizObjVisitor se contente d’appeler la méthode VisitBizObj du visiteur CsvSave reçu en paramètre:
Visitor.VisitBizObj(Self);
Comme Visitor est de type TCsvTextFileSave, c’est le VisitBizObj de TCsvTextFileSave qui est déclenché avec comme paramètre Self qui se réfère à People qui a lancé AcceptBizObjVisitor.
Finalement le visiteur est détruit, ce qui déclenche la SaveToFile de la StringList.
Et le tour est joué !
Il n’y a plus qu’à adapter ce mécanisme aux fichiers avec des champs de longueur fixe.
Solution dans le prochain billet.