POO et PHP, Partie 2
La programmation orientée objet sous PHP, la suite
Nous voici dans la seconde partie de cet article dédié a la programmation orientée objet sous PHP.
Voici le lien vers la partie 1 pour ceux qui ne l'auraient pas lue :
Partie 1
Pour rappel, nous y avions évoqué, entre autres, les notions d'objet, de classe et d'héritage. Nous allons,
dans cette partie, voir les nouvelles notions suivantes : la surcharge, le polymorphisme et les interfaces.
Vous aurez ainsi toutes les bases nécessaires pour continuer à explorer la réalisation d'un framework web, qui constitue
le fil rouge de ce parcours.
Rappel de l'exemple
Nous avions utilisé dans la partie 1 l'exemple de personnages de jeux de rôle. Cet exemple est, en général, bien
adapté aux notions de classes et d'héritage. Nous allons poursuivre avec notre barbare et notre sorcier, en repartant
toutefois d'une base plus épurée pour nous permettre de nous concentrer sur le sujet qui nous intéresse.
Voici le code duquel nous allons repartir :
abstract class Personnage {
protected $nom;
protected $niveau;
function __construct($nom = "inconnu") {
$this->nom = $nom;
$this->niveau = 10;
echo nl2br("Bienvenue $nom !\n");
}
}
class Barbare extends Personnage {
private $puissance;
function __construct($nom = "inconnu", $puissance = 0) {
parent::__construct($nom);
$this->puissance = $puissance;
echo nl2br("Vous etes un barbare niveau ".$this->niveau." !\n");
}
}
class Sorcier extends Personnage {
private $magie;
function __construct($nom = "inconnu", $magie = 0) {
parent::__construct($nom);
$this->magie = $magie;
echo nl2br("Vous etes un sorcier niveau ".$this->niveau." !\n");
}
}
$persoA = new Barbare('Hercule', 7);
$persoB = new Sorcier('Merlin', 7);
Comme vous le voyez, pour éclaircir le tout, nous n'avons laissé que 2 attributs "nom" et "niveau", et enlevé la génération dynamique des accesseurs et mutateurs. Le code ci-dessus va afficher ceci à l'exécution :
Bienvenue Hercule ! Vous etes un barbare niveau 10 ! Bienvenue Merlin ! Vous etes un sorcier niveau 10 !
Surcharge et polymorphisme d'héritage
Nous allons, pour illustrer les notions de surcharge et polymorphisme, créer un nouvelle méthode dans la classe mère "Personnage". Nous voulons
en effet pouvoir appeler une méthode levelUp afin d'augmenter le niveau des personnages. Puisque cette méthode sera commune à toutes les classes
de personnage, nous allons donc la créer dans la classe mère "Personnage". Le but de cette fonction est simple, incrémenter le niveau de 1.
Voici ce que cela donne dans notre classe Personnage :
abstract class Personnage {
protected $nom;
protected $niveau;
function __construct($nom = "inconnu") {
$this->nom = $nom;
$this->niveau = 10;
echo nl2br("Bienvenue $nom !\n");
}
public function levelUp(){
$this->niveau += 1;
}
}
Cette nouvelle méthode "levelUp" étant public, nous pourrons l'appeler depuis chaque instance de barbare ou sorcier, comme ceci par exemple :
$persoB = new Sorcier('Merlin', 7);
$persoB->levelUp();
L'intérêt d'avoir créé cette méthode dans la classe mère réside dans le fait que si le traitement est identique quelle que soit la classe fille, alors il sera inutile de reconduire cette méthode systématiquement. Seulement, nous pourrions vouloir réserver un traitement diffèrent à une classe en particulier. Par exemple, pour le sorcier, plutôt que d'incrémenter de 1, nous voudrions incrémenter de 2. Dans ce cas, il nous suffit, dans la classe sorcier, et dans celle-ci uniquement, de réécrire la méthode levelUp, comme ceci :
abstract class Personnage {
protected $nom;
protected $niveau;
function __construct($nom = "inconnu") {
$this->nom = $nom;
$this->niveau = 10;
echo nl2br("Bienvenue $nom !\n");
}
public function levelUp(){
$this->niveau += 1;
}
}
class Sorcier extends Personnage {
private $magie;
function __construct($nom = "inconnu", $magie = 0) {
parent::__construct($nom);
$this->magie = $magie;
echo nl2br("Vous etes un sorcier niveau ".$this->niveau." !\n");
}
public function levelUp(){
$this->niveau += 2;
}
public function getLevel(){
echo nl2br("Votre niveau ".$this->niveau." !\n");
}
}
$persoB = new Sorcier('Merlin', 7);
$persoB->levelUp();
$persoB->getLevel();
A l'appel de levelUp, c'est bien la méthode du sorcier et non de sa classe mère Personnage qui sera exécutée. Par conséquent le niveau édité sera 12 et non 11. Nous venons de surcharger la méthode levelUp. Plus précisément cela s'appelle du polymorphisme d'héritage ou encore de la spécialisation qui consiste à redéfinir une méthode dans une sous-classe.
Polymorphisme paramétrique
Le polymorphisme d'héritage que nous venons de voir est à distinguer du polymorphisme paramétrique qui va consister à redéfinir au
sein d'une même classe la même fonction (comprenez une fonction ayant la même signature) mais disposant d'un nombre d'arguments différent.
PHP diffère quelque peu d'autres langages dans la manière dont peut être implémenté le polymorphisme paramétrique. Il faudra en effet passer par
des fonctions dites magiques de type __call, comme celle que nous avons utilisée en fin de partie 1.
Voici un exemple de polymorphisme paramétrique dans lequel nous supprimons d'une part la méthode levelUp de la classe sorcier, mais surtout nous remplaçons cette même méthode au sein de la classe mère Personnage par une fonction __call :
abstract class Personnage {
protected $nom;
protected $niveau;
function __construct($nom = "inconnu") {
$this->nom = $nom;
$this->niveau = 10;
echo nl2br("Bienvenue $nom !\n");
}
function __call($name_of_function, $arguments) {
if($name_of_function == 'levelUp') {
switch (count($arguments)) {
case 0:
$this->niveau += 1;
break;
case 1:
$this->niveau += $arguments[0];
break;
}
}
}
}
class Sorcier extends Personnage {
private $magie;
function __construct($nom = "inconnu", $magie = 0) {
parent::__construct($nom);
$this->magie = $magie;
echo nl2br("Vous etes un sorcier niveau ".$this->niveau." !\n");
}
public function getLevel(){
echo nl2br("Votre niveau ".$this->niveau." !\n");
}
}
$persoB = new Sorcier('Merlin', 7);
$persoB->levelUp();
$persoB->getLevel();
$persoB->levelUp(3);
$persoB->getLevel();
Bienvenue Merlin ! Vous etes un sorcier niveau 10 ! Votre niveau 11 ! Votre niveau 14 !
Lors du déroulé du programme en fin de code, vous voyez que nous appelons 2 fois la méthode levelUp. La première fois sans argument, la seconde
fois en précisant un pas de 3. Ces appels vont être redirigés vers la méthode __call de la classe Personnage. Dans celle-ci nous allons regarder
le nom de la méthode appelée. S'il s'agit de levelUp alors nous exécutons le traitement adéquat en fonction du nombre de paramètres, c'est à dire une
incrémentation de 1 pas si aucun argument n'a été précisé, une incrémentation du nombre de pas demandés dans le cas contraire.
Voici ce que nous pouvons dire du polymorphisme.
Interfaces
Les classes Barbare et Sorcier ont toutes deux hérité de la classe Personnage. Celle-ci est une classe abstraite, ce qui signifie qu'elle ne
peut être instanciée, seules ces classes filles le pourront. Nous ne l'avons pas encore précisé mais une classe ne peut hériter que d'une seule
classe mère. Ceci nous amène a la notion d'interface, car en effet, classes abstraites et interfaces sont souvent comparées. Dans les faits, a l'instar
d'une classe abstraite, une interface ne peut être instanciée. Par ailleurs, une classe n'hérite pas d'une interface mais implémente celle-ci.
Enfin, une classe peut implémenter plusieurs interfaces.
Pour la comparaison, c'est après tout ce que l'on peut dire, car interface et classe abstraite ne visent pas le même objectif. Il faut en effet
voir une interface comme un contrat. Elle dicte en quelque sorte à la classe qui lui est associée ce que celle-ci doit implémenter. Ainsi,
une interface va ressembler à ceci :
interface accessDB
{
public function connection($arg1, $arg2);
public function disconnection($arg1);
}
Comme vous le voyez, l'interface se contente de lister des méthodes, arguments inclus. Il appartient aux classes qui lui sont affectées d'implémenter le contenu de chacune de ces méthodes, comme ceci par exemple :
class accessMySQL implements accessDB
{
public function connection($arg1, $arg2)
{
//ici le code de connection a une base MySQL
}
public function disconnection($arg1)
{
//ici le code de deconnection de la base
}
}
A quoi tout cela peut-il bien servir ? Disons qu'une interface nous garantit que toutes les classes qui les implémentent possèdent les mêmes méthodes. Par conséquent elles sont principalement utilisées pour implémenter des classes interchangeables. Par exemple, si nous souhaitons écrire une classe d'accès a une base de données mysql, puis autre dédiée a une base MariaDB, on veillera à utiliser une interface afin que l'application fasse abstraction de la base et puisse faire appel indifféremment aux mêmes méthodes. L'exemple déroulé précédemment en est l'illustration. Ainsi l'interface accessDB va dicter au développeur les méthodes que les classes dédiées a chacun des moteurs de bases de données doivent implémenter. Si bien que l'application n'aura plus à se soucier de savoir sur quelle base elle est connectée, les méthodes employées seront les mêmes, bien que le code exécuté soit diffèrent.
Concusion
Nous avons, au cours de ces deux parties, survolé la programmation orientée objet. Celle-ci se révèle en effet très subtile et on peut vite monter une usine à gaz. Aussi il est bon savoir qu'il existe des architectures (design patterns) prédéfinies, qui font office de Framework lorsqu'il s'agit d'implémenter une application. Ces design patterns constituent des solutions éprouvées pour résoudre des problèmes récurrents. On peut citer par exemple le singleton, l'abstract factory, le decorator, l'iterator ou encore le strategy. Il en existe beaucoup d'autres.
Retrouvez dans la rubrique "Nos datasets" toutes les données dont vous aurez besoin pour tester et pratiquer !