Pourquoi un constructeur ou une fabrique d’objets ?
Une classe sert de modèle à des instances, comme un type à des données. Pour qu’un objet (donc, une instance d’une classe donnée) soit réellement indépendant, il faut que l’on puisse garantir que chaque instance sera confectionnée de manière cohérente, prête à servir. Un objet qui, après sa création nécessiterait encore des mesures d’initialisation particulières serait, au mieux, une mauvaise application de techniques procédurales. Les techniques de programmation orientées objets incluent un ou plusieurs segments de code qui permettent l’initialisation, de manière implicite, des instances. Cette initialisation sera transparente dans la mesure du possible- à l’utilisateur de l’objet.
Le constructeur d’objets
Le constructeur est la manière la plus populaire d’implémenter un mécanisme d’initialisation. Il implique, à chaque création d’un nouvel objet (“d’une nouvelle instance d’une classe”) l’appel implicite du code implémentant le constructeur. Selon les langages, cet appel peut ne pas être totalement implicite : Java demande un appel explicite, alors que C++, selon les cas, peut se contenter d’un appel implicite ou requérir un appel explicite.
Le constructeur ressemble, par certains côtés, à la clause d’initialisation que l’on trouve dans certains langages dits “modulaires” (p. ex. MODULA-2). Mais un module est généralement toujours initialisé de la même façon, alors qu’une instance est initialisée individuellement : c’est l’objet que l’on initialise, non la classe.
De fait, en programmation orientée objets, on a souvent deux opérations d’initialisation distinctes : l’une met en place des caractéristiques communes à toutes les instances (initialisation de la classe), l’autre spécifie les caractéristiques individuelles de l’objet.
Constructeur et héritage
Lors d’une relation d’héritage, les choses se compliquent !
La classe dérivée est un cas particulier (une spécialisation) de la classe de base; elle doit donc, d’une certaine manière, contenir la classe de base. En toute logique, il faut donc, pour qu’une instance de la classe dérivée puisse exister, que l’instance correspondante de la classe de base ait été correctement construite.
En conséquence, il est donc nécessaire qu’une classe dérivée soit en mesure de construire une instance de sa classe de base.
En réalité, si on a correctement utilisé la notion d’héritage, ceci ne devrait poser aucun problème.
Destructeur
Si un constructeur est nécessaire, il faut, corollairement, également définir un destructeur (implicitement ou explicitement invoqué, comme le constructeur).
Le destructeur a pour rôle de défaire tout ce qui a été fait au cours de la “vie” de l’objet.
Cette destruction consiste essentiellement en libération de mémoire réservée, mais aussi en la finalisation de ressources, comme des fichiers, des connexions TCP-IP, des séances MIDI, etc… De fait, il s’agit de restituer l’état de l’environnement d’exécution comme il l’était avant l’existence de l’objet.
La majorité des langages «OO» définit un constructeur – et également un destructeur par défaut. Il en va ainsi de C++ et de Java. Il faut se méfier de ces mécanismes, car le constructeur par défaut ne fait que rarement le travail que l ’on aurait effectivement souhaité de la part d’un constructeur.
Pour un langage purement procédural, le constructeur n’a pas de raison d’être (sinon pour effectuer la réservation de mémoire) en raison de l’absence de tout lien entre les données et le code censé manipuler ces données.
La notion de classe non instanciable (ou abstraite)
Une classe peut ne pas être instanciable. Ainsi, dans la hiérarchie de la figure4.2, page45, il ne serait pas raisonnable d’instancier un objet de la classe Vénicule. Il s’agit d ’un objet générique, qui ne peut en aucun cas correspondre à un objet réel..
Constructeurs et fabriques d’objets (144 KO) (Cours PDF)