Aller au menu - Aller au contenu

Icône La sauvegarde d'objets

Avatar
Mise à jour : 13/12/2010
Difficulté : Facile Facile Creative Commons BY-NC-SA
23 410 visites depuis 7 jours, dont 238 sur ce chapitre classé 14/786
Attaquons désormais la sauvegarde d'objets.

Vous avez vu comment créer vos objets, vous avez aussi vus comment sauvegarder des données dans des fichiers. Mais on ne peut pas simplement écrire un objet dans un fichier comme s'il s'agissait d'une simple chaîne de caractères, il faut passer par une méthode particulière.

Mesdames, Messieurs les Zeros, je vous présente la sérialisation.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

La sérialisation, c'est quoi ?

Vous vous souvenez du cycle de vie d'une variable normale, dés que la boucle, fonction, classe dans laquelle elle a été crée est terminée, la variable est détruite et libérée de la mémoire. Les données qu'elle contient sont donc perdues.

Vous avez dus vous douter que le même principe s'appliquait pour les objets. Et là ce n'est pas une simple valeur de variable qui est perdue mais toutes les variables que l'objet contenant (des attributs). Pour sauvegarder notre variable pour une utilisation ultérieure (un autre lancement du programme), nous avons vus comment stocker cette variable dans un fichier.

Les integers, strings, dates, bref les variables basiques sont facilement stockables mais les objets le sont plus difficilement.

Je vais créer une classe basique contenant des informations concrètes. Le projet que j'ai crée est de type console car l'interface graphique est superflue pour le moment.

Code : VB.NET
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Public Class Film

    Public Titre As String
    Public Annee As Integer
    Public Desciption As String

    Sub New()

    End Sub

    Sub New(ByVal TitreFilm As String, ByVal AnneeFilm As Integer, ByVal DescriptionFilm As String)
        Titre = TitreFilm
        Annee = AnneeFilm
        Desciption = DescriptionFilm
    End Sub

End Class


Ma classe s'appelle Film, elle va contenir des informations pour créer un objet Film auquel on spécifiera son nom, son année de sortie et une petite description.

Cette classe est très basique, seulement trois attributs. Mais si je veux la sauvegarder il va déjà falloir écrire 3 variables différentes dans un fichier. Donc imaginez avec plusieurs dizaines d'informations (attributs).

Image utilisateur


Avec la sérialisation (le stockage d'objet) on va pouvoir facilement écrire tous les attributs d'un objet dans un fichier. Ce fichier sera automatiquement formaté. Ce formatage va dépendre de la méthode de sérialisation utilisée.

Il existe donc deux méthodes.

La méthode binaire, elle permet la sauvegarde des attributs Publics et Privés tout en incluant également le nom de la classe dans le fichier.
La seconde méthode, la sérialisation XML est très utile car le format soap (le type de formatage d'un fichier XML) est très répandu, pour communiquer via des Webservices et en général sur l'internet. Ses inconvénients sont que les attributs privés ne sont pas pris en compte et les types des attributs ne sont enregistrés nulle part.

Commençons avec la sérialisation Binaire :

La sérialisation binaire.

Pour commencer je modifie notre classe Film pour spécifier au programme qu'il peut la sérialiser grâce à <Serializable()> inscrit dans sa déclaration :

Code : VB.NET
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<Serializable()>
Public Class Film

    Public Titre As String
    Public Annee As Integer
    Public Desciption As String

    Sub New()

    End Sub

    Sub New(ByVal TitreFilm As String, ByVal AnneeFilm As Integer, ByVal DescriptionFilm As String)
        Titre = TitreFilm
        Annee = AnneeFilm
        Desciption = DescriptionFilm
    End Sub

End Class


Ensuite, dans la page contenant le code qui va permettre de sérialiser l'objet on va faire deux Imports, l'un permettant d'utiliser la sérialisation, le second permettant l'écriture dans un fichier :

Code : VB.NET
1
2
Imports System.Runtime.Serialization.Formatters.binary
Imports System.IO


Je ne sais pas si vous vous souvenez de la partie 1 sur les fichiers mais elle était fastidieuse car je vous faisais travailler sur des flux. La sérialisation reprend le même principe, nous allons ouvrir un flux d'écriture sur le fichier et la fonction de sérialisation va se charger de le remplir.

Donc pour remplir un fichier bin avec un objet que je viens de créer :

Code : VB.NET
1
2
3
4
5
6
7
8
9
'On crée l'objet
            Dim Avatar As New Film("Avatar", 2009, "Avatar, film de James Cameron sorti en décembre 2009.")
            'On crée le fichier et récupère son flux
            Dim FluxDeFichier As FileStream = File.Create("Film.bin")
            Dim Serialiseur As New BinaryFormatter
            'Serialisation et écriture
            Serialiseur.Serialize(FluxDeFichier, Avatar)
            'Fermeture du fichier
            FluxDeFichier.Close()


Et pourquoi .bin l'extension du fichier ?

Ce n'est pas obligatoire de lui assigner cette extension, c'est juste une convention. Comme lorsque nous créions des fichiers de configuration, nous avions tendance à les nommer en .ini, même si nous aurions pu leur donner l'extension .bla.

Le BinaryFormatter est un objet qui va se charger de la sérialisation Binaire. C'est lui qui va effectuer le formatage spécifique à ce type de sérialisation.

L'objet Avatar étant un film est désormais sauvegardé, si je veux récupérer les informations il faut que je le désérialise.

La désérialisation est l'opposé de la sérialisation. Sur le principe de la lecture / écriture dans un fichier. La sérialisation écrit l'objet, la désérialisation le lit. Nous allons utiliser le même BinaryFormatter que lors de la sérialisation.

Programmatiquement :

Code : VB.NET
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
If File.Exists("Film.bin") Then
            'Je crée ma classe "vide"
            Dim Avatar As New Film()
            'On ouvre le fichier et recupere son flux
            Dim FluxDeFichier As Stream = File.OpenRead("Film.bin")
            Dim Deserialiseur As New BinaryFormatter()
            'Désérialisation et conversion de ce qu'on récupère dans le type "Film"
            Avatar = CType(Deserialiseur.Deserialize(FluxDeFichier), Film)
            'Fermeture du flux
            FluxDeFichier.Close()
        End If


Je vérifie avant tout que le fichier est bien présent. La ligne de désérialisation effectue deux opérations : la désérialisation et la conversion avec Ctype de ce qu'on récupère dans le type de notre classe (ici Film). Et on entre le tout dans "Avatar". Si vous analysez les attributs, vous remarquez que notre film a bien été récupéré.

VB.Net est "intelligent", si vous n'aviez pas effectué de Ctype, il aurait tout de même réussi à l'insérer dans l'objet Avatar. La seule différence est que le Ctype offre une meilleure vue et compréhension de notre programme. La même remarque peut être faite lors de renvoie de valeurs via des fonctions, le type de retour n'est pas forcé d'être spécifié, une variable de type Object (spécifique à Visual Basic) sera alors retourné mais pour un programmeur, un code avec des "oublis" de ce type peut très vite devenir incompréhensible et ouvre la porte à beaucoup de possibilité d'erreur.

Bon, je ne vous montre pas le contenu du fichier résultant, ce n'est pas beau à voir. Ce n'est pas fait pour ça en même temps, la sérialisation XML quand à elle va nous donner un résultat plus compréhensible et modifiable manuellement par un simple mortel.

Voyons ça tout de suite.

La sérialisation XML

La sérialisation XML quand à elle respecte le protocole SOAP (Simple Object Access Protocol), le fichier XML que vous allez générer va pourvoir être transporté et utilisé plus facilement sur d'autres plateformes et langages de programmations qui respecteront eux aussi le protocole SOAP.

Le format XML (je vous renvoie à sa page Wikipedia pour plus d'informations) formate le fichier sous une méthode bien spécifique composée de sections et sous-sections.

Je vais reprends l'exemple de Wikipedia pour vous détailler rapidement sa composition :

Code : XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
  <!-- '''Commentaire''' -->
  <élément-document xmlns="http://exemple.org/" xml:lang=";fr">
    <élément>Texte</élément>
    <élément>élément répété</élément>
    <élément>
      <élément>Hiérarchie récursive</élément>
    </élément>
    <élément>Texte avec<élément>un élément</élément>inclus</élément>
    <élément/><!-- élément vide -->
    <élément attribut="valeur"></élément>
  </élément-document>


La première ligne spécifiant une instruction de traitement, elle est nécessaire pour des logiciels traitant ce type de fichier, une convention encore une fois.
Viens une ligne de commentaire, elle n'est pas censée nous intéresser car notre sérialisation ne génèrera pas de commentaire.
Par contre le reste est intéressant. La sérialisation va convertir la classe en un noeud principal (élément racine) et ses attributs seront transformés en sous-éléments.

Un exemple simple du résultat de la sérialisation :


Code : VB.NET
1
2
3
Public Class ClasseExemple
    Public ValeurNumerique As Integer
End Class


Code : XML
1
2
3
<ClasseExemple>
    <ValeurNumerique>23</ValeurNumerique>
</ClasseExemple>



Vous arrivez à distinguer à l'oeil nu les corrélations qui sont présentes entre la classe et sa sérialisation, ce qui va être beaucoup plus facile pour les modifications manuelles.

Bon, passons à la programmation.

On va changer un l'import que nous utilisions pour la sérialisation binaire.

Ce qui nous amène à écrire :

Code : VB.NET
1
2
Imports System.IO
Imports System.Xml.Serialization


System.IO contient toujours de quoi interagir avec les fichiers et System.Xml.Serialization les classes nécessaires à la sérialisation XML.

Et dans notre code, presque aucun changement :

Code : VB.NET
1
2
3
4
5
6
7
8
Dim Avatar As New Film("Avatar", 2009, "Avatar, film de James Cameron sorti en décembre 2009.")
'On crée le fichier et récupère son flux
Dim FluxDeFichier As FileStream = File.Create("Film.xml")
Dim Serialiseur As New XmlSerializer(GetType(Film))
'Serialisation et écriture
Serialiseur.Serialize(FluxDeFichier, Avatar)
'Fermeture du fichier
FluxDeFichier.Close()


Eh oui, le principe de sérialisation reste le même, c'est juste un objet de type XmlSerializer qu'on instancie cette fois-ci. Il faut passer un argument à son constructeur : le type de l'objet que nous allons sérialiser. Ici c'est ma classe film donc la fonction GetType(Film) convient parfaitement.

Une fois la sérialisation effectuée, le fichier en résultant contient :

Code : XML
1
2
3
4
5
6
<?xml version="1.0"?>
<Film xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Titre>Avatar</Titre>
  <Annee>2009</Annee>
  <Desciption>Avatar, film de James Cameron sorti en décembre 2009.</Desciption>
</Film>


Un beau fichier bien formaté !

Pour la désérialisation, même principe :

Code : VB.NET
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
If File.Exists("Film.xml") Then
    'Je crée ma classe "vide"
    Dim Avatar As New Film()
    'On ouvre le fichier et recupere son flux
    Dim FluxDeFichier As Stream = File.OpenRead("Film.xml")
    Dim Deserialiseur As New XmlSerializer(GetType(Film))
    'Désérialisation et convertion de ce qu'on récupère dans le type "Film"
    Avatar = CType(Deserialiseur.Deserialize(FluxDeFichier), Film)
    'Fermeture du flux
    FluxDeFichier.Close()
End If


Juste un petit mot sur la taille du fichier résultant.

Même si le fichier XML semble plus long leur taille ne diffère que du type de formatage qu'utilise XML.

Si votre objet contient 1Ko de données programmatiquement parlant, la sérialisation n'est pas là pour réduire cette taille. Au contraire, le formatage qu'apporte la sérialisation (binaire ou XML) ne tend qu'à l'alourdir.

La sérialisation multiple

Bon, jusqu'à présent aucun problème pour ce qui est de sérialiser notre petit film "Avatar".

Mais immaginez que nous voulons enregistrer toute une bibliothèque de films (une idée de TP ? :) ).

Déjà, avant que vous vous enfonciez je vous dis qu'avec notre méthode ça ne va pas être possible.

Et pourquoi ça ?


Eh bien vous avez vus que le noeud principal de notre document XML est <Film>, et j'ai dit qu'il ne pouvait avoir qu'un seul nœud principal par document XML. autrement dit, qu'un seul film.
Certes il reste l'idée de créer un fichier XML par Film à sauvegarder mais je pense que ce n'est pas la meilleure méthode :) .

Rappelez vous de nos amis les tableaux. Un tableau de string, vous vous souvenez, et dans une TP je vous ai tendu un piège en faisant un tableau de RadioButtons.

Ça va être le même principe ici, un tableau de films.

Oui, il fallait juste y penser. Ce qui nous donne en création d'un tableau de films et sérialisation du tout :

Code : VB.NET
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Dim Films(1) As Film
Films(0) = New Film("Avatar", 2009, "Avatar, film de James Cameron sorti en décembre 2009.")
Films(1) = New Film("Twilight 3", 2010, "Troisième volet de la quadrilogie Twilight")
'On crée le fichier et récupère son flux
Dim FluxDeFichier As FileStream = File.Create("Films.xml")
Dim Serialiseur As New XmlSerializer(GetType(Film()))
'Serialisation et écriture
Serialiseur.Serialize(FluxDeFichier, Films)
'Fermeture du fichier
FluxDeFichier.Close()


Pour la création du tableau, je ne vous fais pas d'explication je pense. Un tableau de 2 cases, Avatar dans la première, Twilight 3 dans la seconde (il va falloir que je mette à jour ce tuto dans le futur, avec les films du moment :p ).

La seule particularité est le type que l'on fournit au XmlSerializer. Ce n'est pas un simple GetType(Film) , autrement dit le type de ma classe Film mais c'est GetType(Film()) , le type d'un tableau de ma classe Film.

Une fois la sérialisation effectuée, notre fichier se compose ainsi :

Code : XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?xml version="1.0"?>
<ArrayOfFilm xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Film>
    <Titre>Avatar</Titre>
    <Annee>2009</Annee>
    <Desciption>Avatar, film de James Cameron sorti en décembre 2009.</Desciption>
  </Film>
  <Film>
    <Titre>Twilight 3</Titre>
    <Annee>2010</Annee>
    <Desciption>Troisième volet de la quadrilogie Twilight</Desciption>
  </Film>
</ArrayOfFilm>


Le nœud principal est désormais un ArrayOfFilm, soit un Tableau de Film. Et chaque nœud secondaire correspond à un film.

La désérialisation est pratiquement la même elle aussi :

Code : VB.NET
1
2
3
4
5
6
7
8
9
If File.Exists("Film.xml") Then
   'On ouvre le fichier et recupere son flux
   Dim FluxDeFichier As Stream = File.OpenRead("Film.xml")
   Dim Deserialiseur As New XmlSerializer(GetType(Film()))
   'Désérialisation et insertion dans le tableau de Film()
   Dim Films() As Film = Deserialiseur.Deserialize(FluxDeFichier)
   'Fermeture du flux
   FluxDeFichier.Close()
End If


Ici, même remarque que pour la sérialisation, le type qu'on fournit est bien un tableau de Film() et non plus un Film simple.
Seconde remarque : j'ai effectué la déclaration de mon tableau sur la même ligne que la désérialisation. Cela me permet de m'affranchir de la déclaration fixe du la longeur de mon tableau.
Autrement dit, le programme va se charger tout seul de déterminer combien il y a de films présents et construira un tableau de taille nécessaire. J'ai fait ceci en écrivant Films() au lieu de Films(1). Mais le résultat sera le même, et au moins pas de risque d'erreurs de taille =)

Et en ce qui concerne les collections, que nous venons d'aborder ; la méthode est la même :

Code : VB.NET
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Imports System.IO
Imports System.Xml.Serialization

Module Module1

    Sub Main()

        Dim MaListeDeClasses As New List(Of Classe)
        MaListeDeClasses.Add(New Classe("Avatar"))
        MaListeDeClasses.Add(New Classe("Twilight 1"))
        MaListeDeClasses.Insert(0, New Classe("Titanic"))

        'On crée le fichier et récupère son flux
        Dim FluxDeFichier As FileStream = File.Create("C:\Classes.xml")
        Dim Serialiseur As New XmlSerializer(GetType(List(Of Classe)))
        'Serialisation et écriture
        Serialiseur.Serialize(FluxDeFichier, MaListeDeClasses)
        'Fermeture du fichier
        FluxDeFichier.Close()

    End Sub

End Module

Public Class Classe

    Private _Variable As String

    Sub New()

    End Sub

    Sub New(ByVal Variable As String)
        _Variable = Variable
    End Sub

    Public Property Variable As String
        Get
            Return _Variable
        End Get
        Set(ByVal value As String)
            _Variable = value
        End Set
    End Property

End Class


Sur le même principe, le GetType s'effectue sur une List(Of Classe). Et le fichier XML résultant a le même schéma qu'un tableau :

Code : XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?xml version="1.0"?>
<ArrayOfClasse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Classe>
    <Variable>Titanic</Variable>
  </Classe>
  <Classe>
    <Variable>Avatar</Variable>
  </Classe>
  <Classe>
    <Variable>Twilight 1</Variable>
  </Classe>
</ArrayOfClasse>


Il serait même préférable à présent d'utiliser des collections, qui sont plus modulaires et qui représentent mieux les concepts de la POO que des tableau (archaïques) :) .
En conclusion, la sérialisation est vraiment très pratique, une simple ligne pour sauvegarder tout un objet.

Sur ce principe, une configuration peut aisément être sauvegardé dans un objet fait par vos soins recensant toutes les valeurs nécessaires au fonctionnement de votre programme puis une sérialisation XML vous donnera un fichier clair et formaté, au même titre qu'un fichier .ini. Même si ce n'est pas son utilisation principale, ça peut être une bonne alternative.
Rappelez vous toutefois que la sérialisation XML n'enregistre pas les attributs privés ! Source d'erreur.


Pour combler cette lacune deux solutions : passer tout ses arguments en public, mais cette technique "tue" le principe de la POO ou alors utiliser des propriétés. Les property. Si votre attribut est privé et que vous avez créé la property publique correspondante, il sera sérialisé.

Le XML est donc un format générique et, il a été conçu pour stocker des données. Lorsque nous aborderons le chapitre sur les bases de données, les premières notions se feront sûrement avec des documents XML, c'est une base de donnée comme une autre ...

Bref, n'allons pas trop vite, il nous reste a finir cette méchante partie d'orienté objet avant d'attaquer ces autres concepts ^^ .
Chapitre précédent Sommaire Chapitre suivant

Partager

2 commentaires pour "La sauvegarde d'objets"
Note moyenne : 3.60 / 4 (543 votes)
Pseudo Commentaire
Hors ligne polmiki # Posté le 21/01/2011 à 17:43:02

Très bon tuto
Merci !
J'ai déjà Quelques Notions en POO, avec le langage Java!
Et pour la sérialisation, c'est presque le même principe !
Hors ligne Florian Galloni # Posté le 28/04/2012 à 11:44:16

Avis : Bon

Bonjour,
votre tutoriel me rend service. Cependant, comment faire pour écrire de nouveaux objets dans le fichier sans effacer les anciens. Dans mon application, une fenêtre me permet de rentrer le modèle d'une voiture ainsi que le nombre d'exemplaires reçu. Je valide. Ces informations sont bien dans mon fichier .bin mais si je rouvre mon application et que je veux rentrer un nouveau modèle, ça efface les anciennes données. Quelle est la commande qui permet d'y conserver ?
Merci d'avance.
Cordialement

Voir tous les commentaires