La programmation objet (concepts fondamentaux)

En programmation, un modèle est une abstraction de la réalité.
Par conséquent, un modèle est une vue subjective de la réalité, mais toutefois, cette vue est toujours pertinente.

En effet, un modèle définit une frontière entre la réalité et la perspective de l'observateur. Il ne s'agit donc pas de la réalité, mais d'une vue très subjective de la réalité.
Par conséquent, un modèle doit permettre de faciliter la compréhension d'un système étudie, mais aussi, de simuler ce système.

Aujourd'hui, en programmation, il existe deux principaux modèle de représentation du monde : le modèle fonctionnel (que vous connaissez déjà) et le modèle objet (que nous allons donc étudier).
Jusqu'à aujourd'hui, vous connaissiez la programmation en PHP avec une approche fonctionnelle.

Afin de mieux comprendre les différences entre ces deux approches, nous allons donc détailler les deux approches, et voir ainsi leurs avantages et leurs inconvénients.

Avec une approche fonctionnelle, vos programmes étaient composés d'une série de fonctions, qui ensemble, assuraient certains services.
Il s'agit d'une approche logique, cohérente et intuitive de la programmation.

Cette approche a un avantage certain que l'on appelle la factorisation des comportements.
En effet, une découpe fonctionnelle intelligente consiste à factoriser certains comportements d'une application, ce qui veut dire que pour créer une fonction d'une application, rien ne vous empêche d'utiliser un autre ensemble de fonctions (qui sont donc déjà écrites).

Mais (il y a toujours un mais ^^), l'approche fonctionnelle a aussi ses défauts, comme par exemple une maintenance complexe en cas d'évolution de votre application.
La factorisation des comportements n'a pas que des avantages. En effet, si on y réfléchit deux minutes, maintenant, nos fonctions sont devenues interdépendantes.

Et ceci implique qu'une simple mise à jour de l'application à un point donné peut impacter en cascade sur d'autres fonctions de notre application.
Naturellement, nous pouvons toujours essayer d'écrire des fonctions les plus génériques possibles mais cela rend le développement de l'application beaucoup plus complexe.

De plus, en cas d'évolution de l'application, même si la structure générale de l'application reste valide, la multiplication des points de maintenance (dus au chaînage des fonctions à cause de la factorisation des comportements) rend l'adaptation extrêmement difficile.
Et dans ce cas, l'application sera alors retouchée dans sa globalité.

Prenons un exemple concret : on a en notre possession une application permettant de gérer une bibliothèque, et suite à la demande du client, notre application doit maintenant être capable de gérer une médiathèque, c'est-à-dire que l'on pourra emprunter, non seulement des livres, mais aussi, des CD-ROM, des DVD, etc...

Pour faire évoluer notre application, nous devrons faire évoluer les structures de données qui sont manipulées par les fonctions, puis nous devrons adapter les traitements, qui à l'origine, ne manipulaient qu'un seul type de document : les livres.

Nous devrons donc faire évoluer toutes les portions de code qui utilisent la base documentaire, et ce, afin de gérer les données et les actions propres aux différents type de documents.

Exemple : notre application, alors qu'elle ne gérait que des livres, pouvait mettre une sorte de carton jaune à un emprunteur lorsqu'il ne rendait pas son livre dans les temps.

Or, maintenant que notre bibliothèque est devenue une médiathèque, et si l'on désire que le délai avant le fameux carton jaune dépende du document emprunté (le carton jaune arrivera plus ou moins tard, suivant le document emprunté : un livre, un CD-ROM, un DVD, etc...), et bien il va falloir prévoir une règle de calcul pour chaque type de document.

Au final, c'est pratiquement la totalité de l'application qu'il va falloir adapter pour gérer les nouveaux types de documents et les traitements correspondants.

Les modifications que nous avons apportées à notre application de médiathèque, nous permettent de penser qu'une approche objet sera beaucoup plus avantageuse quant à la réutilisation du code déjà écrit.

Mais qu'est ce qu'un objet ?

Un objet est une entité comportant des frontières précises et qui possède une identité (un nom).

De plus, un ensemble d'attributs caractérisent l'état d'un objet, et l'on dispose d'un ensemble d'opérations (les méthodes) qui permettent d'agir sur le comportement de notre objet.

Un objet est l'instance d'une classe, et une classe, est un type de données abstrait, caractérisé par des propriétés (ses attributs et ses méthodes) communes à des objets et elle permet de créer des objets possédant ces propriétés.

Exemple : prenons le cas de notre bibliothèque.

Créons alors une classe Livre qui va nous permettre de créer des objets "Livre".
Et une instance de la classe Livre (un objet).

titre et auteur sont les attributs de notre classe, c'est-à-dire les caractéristiques communes à tous nos objets "Livre".
En clair, tous les livres auront un titre et un auteur.

De plus, notre classe dispose des méthodes emprunter() et rendre() qui s'appliquerons à nos objets "Livre" (on agira alors directement sur notre objet "Livre").

monLivre représente quant à lui, une instance de la classe Livre, il s'agit donc d'un objet qui porte le nom monLivre.

Les autres concepts importants de l'approche objet sont :
  • l'encapsulation
  • l'héritage (ainsi que le polymorphisme)
  • l'agrégation




L'encapsulation consiste à masquer les détails d'implémentation d'un objet, et ce, en définissant une interface. En clair, vous n'avez pas besoin de savoir comment est conçu cet objet à la base pour pouvoir l'utiliser. Une interface est quant à elle, une vue externe d'un objet et elle définit les services accessibles pour modifier le comportement de l'objet.
L'encapsulation facilite l'évolution d'une application car elle stabilise l'évolution des objets.
En effet, bous pouvons très bien modifier l'implémentation des attributs d'un objet sans pour autant modifier son interface. Elle garantit de plus l'intégrité des données vu qu'elle permet d'interdire l'accès direct aux attributs des objets (on doit alors passer par des assesseurs).
Un assesseur étant une méthode d'accès pour connaître ou modifier la valeur d'un attribut d'un objet.

L'héritage est un mécanisme de transmission des propriétés d'une classe (ses attributs et ses méthodes) vers une sous-classe (la sous-classe héritant de la classe principale).
Grâce à l'héritage, une classe peut aussi être spécialisée en d'autres classes, afin d'y ajouter des caractéristiques spécifiques (ajout de méthodes par exemple) ou d'en adapter certaines.
Plusieurs classes peuvent aussi être généralisées en une classe qui les factorise, et ce, afin de regrouper les caractéristiques communes d'un ensemble de classes.
La spécialisation et la généralisation permettent de construire des hiérarchies de classes.
L'avantage principal de l'héritage est qu'il vous permet de d'éviter la duplication de code, et il encourage à la réutilisation de même code.

La classe Livre hérite des propriétés de la classe Document.

Ce qui veut dire que les objets Livre auront un auteur, un éditeur, mais aussi un numéro et un titre.
De plus, comme on peut emprunter() ou rendre() un document, on pourra également emprunter() et rendre() un livre (ceci, parce que la classe Livre hérite de la classe Document).
On pourra également lire() ou photocopier() un livre.
En revanche, un document n'aura pas d'auteur ni d'éditeur, et on ne pourra pas le lire, ni même le photocopier (ces propriétés font partie de la classe Livre, or un document n'est pas un livre : c'est un livre qui est un document, et non l'inverse).

Quand au polymorphisme, celui-ci représente la faculté d'une méthode à pouvoir s'appliquer à des objets de classes différentes. Il augmente donc la généricité de votre code.

Chacune des trois classes (Voiture, Train et Avion) héritent des méthodes de la classe Véhicule.
Ces trois classes, auront donc accès à la méthode deplacer() de la classe Véhicule.
Or, dans ces trois "sous classes", nous avons choisit de redéfinir la méthode deplacer() de la classe Véhicule.
Nous adaptons en fait cette méthode suivant l'objet que nous étudions (un avion se déplace dans les airs, un train sur des rails et une voiture sur la route).

L'agrégation constitue une relation entre deux classes, spécifiant que les objets d'une classe sont des composants de l'autre classe.
Une relation d'agrégation permet donc de définir des objets composés d'autres objets.
L'agrégation permet d'assembler des objets de base, afin de construire des objets plus complexes.

Notre objet eau de type molécule est en fait une combinaison de trois objets :
  • hydrogene1 qui est un objet de la classe atome (c'est une instance de la classe atome).
  • hydrogene2 qui est un objet de la classe atome (c'est une instance de la classe atome).
  • oxygene qui est un objet de la classe atome (c'est une instance de la classe atome).


Notre molécule est donc compose de trois atomes : hydrogene1, hydrogene2 et oxygene.

En conclusion, vous devez savoir que le concept objet est un concept stable et éprouvé.
C'est un concept ancien (Simula, le premier langage de programmation à implémenter le concept de type abstrait à l'aide de classe date de 1967), mais il n'a jamais été autant d'actualité.

A cela, deux raisons principales :
  • l'approche fonctionnelle n'est pas adaptée au développement d'applications qui évoluent sans cesse et dont la complexité croit continuellement (plusieurs dizaines de milliers de lignes de code).
  • l'approche objet a été inventée pour faciliter l'évolution d'applications complexes.


De nos jours, les outils orientés objets sont fiables et performants (les compilateurs C++ par exemple produisent un code robuste et optimisé).

Mais, malgré les apparences, il est beaucoup plus naturel pour nous, êtres humains, de décomposer un problème informatique sous forme d'une hiérarchie de fonctions atomiques et de données, qu'en terme d'objets et d'interaction entre ces objets.
De plus, le vocabulaire précis est souvent un facteur d'échec important dans la mise en oeuvre d'une approche objet.

C'est pour cela qu'il faut penser objet des le départ, au lieu d'essayer de convertir une série de fonction en une classe.
LoadingChargement en cours