Les classes abstraites et finales

Rechercher

Les classes abstraites et finales

  • Par Emacs
  • 15 commentaires
  • 27531 lectures
  • RSS -  Atom

PHP intègre un autre concept de la programmation orientée objet : les classes abstraites. Ce cours définit et introduit la notion de classes abstraites. Nous présenterons ce que sont les classes abstraites, à quoi elles servent au développement et comment les déclarer et les utiliser. Nous étudierons enfin le cas particulier des classes et méthodes finales qui participent à la sécurité du code en programmation orientée objet.

Présentation et déclaration des classes abstraites

Définition des classes abstraites

Les classes abstraites s'inscrivent davantage dans la sûreté de la programmation orientée objet. La première particularité d'une classe abstraite, c'est qu'elle ne peut être instanciée (et donc créer un objet). De cette affirmation, on en déduit logiquement qu'une classe abstraite est déclarée afin d'être dérivée par des classes concrètes.

Une classe abstraite se comporte comme une classe concrète typique. C'est-à-dire qu'elle peut déclarer des attributs et des méthodes traditionnels qui seront accessibles dans les classes dérivées. En fonction bien sûr de la visibilité choisie (public, private et protected) pour chacun des attributs et méthodes.

Jusque là, il n'y a aucun changement par rapport aux classes concrètes si ce n'est le fait que l'on ne puisse pas instancier les classes abstraites. C'est là qu'interviennent les méthodes abstraites. Nous verrons par la suite qu'une classe déclarée abstraire peut aussi définir des méthodes abstraites. Ces dernières devront obligatoirement être redéfinies dans les classes dérivées. C'est un moyen de s'assurer que la classe dérivée adoptera le comportement désiré.

Déclaration d'une classe abstraite

La déclaration d'une classe abstraite se réalise au moyen du mot-clé « abstract ». Prenons l'exemple d'une classe abstraite « EtreHumain » qui sera, par exemple, dérivée par deux classes concrètes « Homme » et « Femme ».

Déclaration d'une classe abstraite
<?php
abstract class EtreHumain
{
/**
* Sexe de la personne
*
* @var string
*/
protected $sexe;
/**
* Nom de la personne
*
* @var string
*/
protected $nom;
/**
* Met à jour le nom
*
* @param string $nom
* @return void
*/
public function setNom($nom)
{
$this->nom = $nom;
}
/**
* Retourne le nom de la personne
*
* @param void
* @return string $nom
*/
public function getNom()
{
return $this->nom;
}
/**
* Retourne le sexe de la personne
*
* @param void
* @return string $sexe
*/
public function getSexe()
{
return $this->sexe;
}
}
?>

Vous remarquez que nous n'avons volontairement pas implémenté de constructeur dans cette classe puisque les classes abstraites ne peuvent être instanciée. Si nous essayons d'instancier cette classe pour créer un objet de type EtreHumain, nous obtiendrons le message d'erreur ci-après :

Fatal error: Cannot instantiate abstract class EtreHumain in /Users/Emacs/Sites/Demo/Tutoriels/abstract.php on line 35

Intéressons nous à présent à l'autre subtilité des classes abstraites : les méthodes abstraites.

Déclaration et redéfinition des méthodes abstraites

Une méthode abstraite est aussi déclarée au moyen du mot-clé « abstract ». C'est une méthode partiellement définie dans la classe. En effet, lorsque l'on déclare une méthode abstraite, on ne définit que son prototype (sa signature). Les classes dérivées devront obligatoirement redéfinir entièrement (prototype + corps) toutes les méthodes abstraites de la classe parente.

Reprenons notre exemple précédent et ajoutons quelques méthodes abstraites à notre classe abstraite « EtreHumain ».

Déclaration de méthodes abstraites

<?php
abstract class EtreHumain
{
/**
* Sexe de la personne
*
* @var string
*/
protected $sexe;
/**
* Nom de la personne
*
* @var string
*/
protected $nom;
/**
* La personne fait du sport
*
* @abstract
*/
abstract function faireDuSport();
/**
* Divertit la personne
*
* @abstract
*/
abstract function seDivertir();
/**
* Met à jour le nom
*
* @param string $nom
* @return void
*/
public function setNom($nom)
{
$this->nom = $nom;
}
/**
* Retourne le nom de la personne
*
* @param void
* @return string $nom
*/
public function getNom()
{
return $this->nom;
}
/**
* Retourne le sexe de la personne
*
* @param void
* @return string $sexe
*/
public function getSexe()
{
return $this->sexe;
}
}
?>

Notre classe dispose à présent de deux nouvelles méthodes abstraites faireDuSport() et seDivertir(). Comme nous pouvons le constater, le corps de ces deux méthodes n'est pas défini. Nous devrons les définir dans les classes dérivées.

Remarque : toute classe qui définit une ou plusieurs méthodes abstraites doit obligatoirement être déclarées abstraite elle aussi.

C'est pourquoi nous allons à présent définir deux classes « Homme » et « Femme » qui hériteront toutes les deux des propriétés et méthodes de notre classe abstraite. Commençons simplement par les déclarer et tenter de les instancier.

Déclaration des classes dérivées

Déclaration des classes dérivées Homme et Femme
<?php
class Homme extends EtreHumain
{
/**
* Construit l'objet Homme
*
* @param string $nom Nom de l'homme
* @return void
*/
public function __construct($nom)
{
$this->nom = $nom;
$this->sexe = 'M';
}
}
class Femme extends EtreHumain
{
/**
* Construit l'objet Femme
*
* @param string $nom Nom de la femme
* @return void
*/
public function __construct($nom)
{
$this->nom = $nom;
$this->sexe = 'F';
}
}
?>

Tentons à présent d'instancier une des deux classes.

<?php
$oBob = new Homme('Bobby');
echo $oBob->getNom();
?>

Bien entendu, nous obtenons l'erreur suivante car nous n'avons pas redéfini les méthodes abstraites de la superclasse.

Fatal error: Class Homme contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (EtreHumain::faireDuSport, EtreHumain::seDivertir) in /Users/Emacs/Sites/Demo/Tutoriels/abstract.php on line 74

Ce message nous indique que nous devons soit redéfinir explicitement les méthodes abstraites de la superclasse ou bien rendre notre classe Homme abstraite. Comme nous souhaitons pouvoir instancier la classe dérivée, il ne nous reste que la première solution. Redéfinissons donc ces deux classes abstraites dans chacune des classes dérivées.

Redéfinition des méthodes abstraites dans les classes dérivées
<?php
class Homme extends EtreHumain
{
/**
* Construit l'objet Homme
*
* @param string $nom Nom de l'homme
* @return void
*/
public function __construct($nom)
{
$this->nom = $nom;
$this->sexe = 'M';
}
/**
* Affiche le sport de l'homme
*
* @param void
* @return void
*/
public function faireDuSport()
{
echo $this->nom .' fait de la boxe';
}
/**
* Affiche la distraction de l'homme
*
* @param void
* @return void
*/
public function seDivertir()
{
echo 'Soirée foot et bières';
}
}
class Femme extends EtreHumain
{
/**
* Construit l'objet Femme
*
* @param string $nom Nom de la femme
* @return void
*/
public function __construct($nom)
{
$this->nom = $nom;
$this->sexe = 'F';
}
/**
* Affiche le sport de la femme
*
* @param void
* @return void
*/
public function faireDuSport()
{
echo $this->nom .' fait du fitness';
}
/**
* Affiche la distraction de la femme
*
* @param void
* @return void
*/
public function seDivertir()
{
echo 'Shopping entre filles';
}
}
?>

Instancions maintenant chacune des deux classes.

Instances des classes Homme et Femme
<?php
$oAlice = new Femme('Alice');
$oAlice->faireDuSport();
echo '<br/>';
$oAlice->seDivertir();
echo '<br/>';
echo 'Sexe : ', $oAlice->getSexe();
echo '<br/><br/>';
$oBob = new Homme('Bobby');
$oBob->faireDuSport();
echo '<br/>';
$oBob->seDivertir();
echo '<br/>';
echo 'Sexe : ', $oBob->getSexe();
?>

Le résultat obtenu est bien celui attendu :

Alice fait du fitness
Shopping entre filles
Sexe : F
Bobby fait de la boxe
Soirée foot et bières
Sexe : M

Pour les instances de la classe Femme, nous retrouvons bien la valeur F pour l'attribut $sexe ainsi que les méthodes abstraites redéfinies qui affiche correctement le fitness et le shopping. Quant aux instance de la classe Homme, le résultat est celui attendu. L'attribut $sexe prend bien la valeur M et les deux méthodes abstraites affichent bien le sport Boxe et le divertissement Soirée football.

Cas particuliers des classes et méthodes finales

Présentation des classes et méthodes finales

Jusqu'à maintenant nous avons présenté les classes et méthodes abstraites. PHP introduit un mécanisme supplémentaire pour assurer la surêté de la programmation. Il s'agit du mot-clé « final » qui peut-être appliqué soit à une classe ou bien à une méthode.

Lorsque l'on définit une classe comme « finale », cela signifie qu'elle ne pourra plus être dérivée par une sous-classe. Cela implique également que ses attributs et méthodes ne pourront plus être redéfinis. En revanche, si l'on applique le mot-clé « final » à une méthode d'une classe, alors c'est uniquement cette méthode qui ne pourra plus être redéfinie dans les classes dérivées.

En interdisant la dérivation d'une classe ou la redéfinition (surchage) des méthodes d'une classe, cela vous permet de vous assurer que le développeur ne contournera pas directement la logique que vous avez mise en place.

Déclaration de classes finales

Reprenons nos 3 classes précédentes. Nous décidons maintenant que les classes « Homme » et « Femme » ne pourront être dérivées. Nous les déclarerons donc comme étant finales. Ce qui nous donne :

Déclaration de classes finales
<?php
final class Homme extends EtreHumain
{
// Suite du code de la classe
}
final class Femme extends EtreHumain
{
// Suite du code de la classe
}
?>

Tentons à présent de dériver l'une des deux classes et de l'instancier. Pour cela, il nous suffit d'écrire une classe « JeuneGarcon » qui hérite des propriétés et méthodes de la classe « Homme ».

<?php
class JeuneGarcon extends Homme
{
/**
* Construit l'objet JeuneGarcon
*
* @param string $nom Nom du jeune garcon
* @return void
*/
public function __construct($nom)
{
parent::__construct($nom);
}
}
?>

Bien entendu une erreur est générée puisque la classe Homme n'est plus dérivable du fait de sa déclaration "finale".

Fatal error: Class JeuneGarcon may not inherit from final class (Homme) in /Users/Emacs/Sites/Demo/Tutoriels/abstract.php on line 153

Remarque : une classe abstraite ne peut-être déclarée "finale". Une erreur de syntaxe sera générée. De plus, déclarer une classe abstraite et finale est synonyme d'un manque de logique puisque le but d'une classe abstraite est d'être dérivée par des classes filles.

Déclaration de méthodes finales

Etudions maintenant le cas des méthodes finales. Reprenons la classe abstraite « EtreHumain » et déclarons les méthodes « getNom() » et « getSexe() » comme finales.

Déclaration de méthodes finales
<?php
abstract class EtreHumain
{
// Attributs et autres méthodes de la classe
// ...
/**
* Retourne le nom de la personne
*
* @param void
* @return string $nom
* @final
*/
public final function getNom()
{
return $this->nom;
}
/**
* Retourne le sexe de la personne
*
* @param void
* @return string $sexe
* @final
*/
public final function getSexe()
{
return $this->sexe;
}
}
?>

A présent les classes dérivées « Homme » et « Femme » ne peuvent plus redéfinir ces deux méthodes. Essayons de surcharger l'une de ces méthodes dans la classe « Homme ».

Redéfinition de méthodes abstraites
<?php
final class Homme extends EtreHumain
{
// Autres méthodes de la classe
// ...
/**
* Retourne le sexe
*
* @param void
* @return string sexe de l'homme
*/
public function getSexe()
{
return 'Masculin';
}
}
?>

Ici, la méthode « getSexe() » ne retourne plus la valeur de l'attribut protégé $sexe mais tente de renvoyer la chaine de caractères « masculin ». On s'en doute, la redéfinition de la méthode génère une erreur fatale par l'interprêteur PHP.

Fatal error: Cannot override final method EtreHumain::getSexe() in /Users/Emacs/Sites/Demo/Tutoriels/abstract.php on line 115

Conclusion

Tout au long de ce cours, nous avons découvert le mécanisme des classes et méthodes abstraites qui permettent de bénéficier des avantages de l'héritage et de s'assurer que les classes dérivées implémentent bien certaines actions.

Enfin nous avons étudié le cas particulier des classes et méthodes finales qui empêchent toute surcharge dans les classes dérivées et assurent une sécurité plus importante du code.



Les commentaires

1. Par Sithran le 11/05/2008 20:56

Je m'y suis repris à deux fois pour la lecture de ce tutoriel. C'est quand même une notion assez difficile à saisir au début, mais ça vient avec la pratique je pense .

Grâce aux tutos d'A-PHP, je me familiarise de plus en plus avec le mode de pensée objet en étant certain de ne pas prendre de mauvaises habitudes. Bravo pour ce tuto !

2. Par Emacs le 11/05/2008 21:00

Merci Sithran La POO ça ne s'apprend pas si facilement que ça. J'essaie de faire en sorte d'être exhaustif et de présenter des exemples simples.

3. Par Sithran le 11/05/2008 21:03

C'est vrai que les exemples rendent les choses beaucoup plus concrètes et comme il s'approche de la "vie réelle", ils permettent de mieux se visualiser les choses.

4. Par Ludal le 23/07/2008 16:47

Bonjour et merci pour ce tuto très clair.
Il me semble qu'une erreur s'y soit glissée:

Dans : "Déclaration des classes dérivées Homme et Femme"
Vous dites : "Tentons à présent d'instancier une des deux classes."

<?php
$oBob = new Homme('Bobby';
echo $oBob->getNom();
?>

Pour moi PHP ne doit pas renvoyer d'erreur.
Par contre si on essaie

<?php
$oBob = new Homme('Bobby';
echo $oBob->faireDuSport();
?>

alors là il y a bien une erreur.

Ou alors j'ai peut être mal compris..

5. Par Ludal le 23/07/2008 16:56

Autant pour moi, je n'avais pas vraiment bien compris.
Il n'y a effectivement pas d'erreur.

Désolé

6. Par Jérémy_B le 12/08/2008 00:02

Si j'ai bien compris les classes finales, elles permettent en faite de mettre un terme à la réutilisation de la classe (ou héritage), alors que les classes abstraites forcent à créer des classes héritantes de la classe abstraite. Par exemple : la classe véhicule peut être une classe abstraite et les classes voiture et moto, les classes héritées. (Est-ce un bon cas de figure ?) Ainsi, pas la peine d'instancier la classe véhicule (qui plus est, serait impossible), mais plutôt celle de la voiture ou de la moto.

PS : Dans " Déclaration de classes finales " --> Remarque : " est synonyme d'un manque de logique puisqu'une le but ", c'est " puisque le but ".

Merci encore pour ce bon tutoriel.

7. Par Emacs le 12/08/2008 00:56

Effectivement tu as bien compris quelle était la différence entre les classes abstraites et les classes finales. La classe abstraite doit obligatoirement être dérivée et ne peut-être instanciée. La classe finale empêche toute nouvelle généralisation (héritage). Cela te permet de t'assurer que l'on ne pourra pas redéfinir des propriétés et méthodes de cette classe dans une classe dérivée.

Merci pour le correctif

8. Par nono le 10/10/2008 11:09

salut à tous,
Si j'ai bien compris c'est une question de sécurité que d'utiliser ce type de class, non? Ainsi pour une class de connection SQL cela peut servir avec des fonction protected ? ou alors j'ai rien compris.

Le ridicule ne tue pas la clope si

9. Par Emacs le 11/10/2008 10:47

Salut Nono,

Ce n'est pas une question de sécurité mais de sûreté de programmation. Ce ne sont pas les mêmes choses. Utiliser les classes abstraites et / ou finales te permet de maîtriser la manière dont sont utilisées tes classes. Avec les méthodes abstraites, tu obliges la dérivation et empêche l'instanciation tandis qu'avec les classes finales, tu interdis toute généralisation.

10. Par Dimebagplan le 03/05/2009 22:10

Bonjour,

Après plusieurs recherches sur le net pour trouver un site qui pourrait m'expliquer à quoi sers une classe abstraite, je trouve mon bonheur ici

Voici des exemples concret et très simples à comprendre.
Les explications sont aussi très bonnes, bravo et mille merci.

J'ai même appris un 3 ème truc , vers la fin il y à parent::__construct($nom);

Si j'ai bien compris, parent::nomDeFonction();

est égale à un super() en java ?
On peux aussi utiliser ça sur un élément non constructeur ? A savoir une fonction ?

Encore mille merci et @bientôt.
Dimebagplan

11. Par Emacs le 03/05/2009 23:59

Merci pour tes compliments. Oui le mot-clé parent permet d'appeler une méthode d'une superclasse. En Java super() est le constructeur parent. Ici parent permet aussi d'appeler une méthode non constructeur des classes parentes.

12. Par Tadaa9 le 26/06/2009 21:02

Salut,
Bon j'ai bien compris le cours. Par contre : il y a un point qui m'embête ! Pourquoi on définit des méthodes abstraites dans une classe abstraites si c'est pour les redéfinir dans la classe dérivée ? Autant ne rien mettre... lol

13. Par Emacs le 28/06/2009 01:19

@tadaa9 : définir une méthode abstraite dans une classe, c'est pour obliger le développeur de l'implémenter dans une classe concrète qui la dérive. Prenons un exemple très simple. Imagine une classe abstraite FormField qui contient une méthode abstraite render(). Cette classe contient les propriétés et méthodes propres à tous les types de champs de formulaires (name, id...). Puis, on crée deux classes concrètes InputTextFieldet SelectField qui dérivent chacune cette classe abstraite. La première doit redéfinir la méthode render() pour qu'elle retourne la balises html complète d'un champ de type "input text" tandis que la méthode render() de la classe SelectField doit retourner le code HTML d'une liste déroulante de type "select".

En somme la méthode abstraite sert à définir un comportement obligatoire à l'objet mais la manière dont ce comportement est réalisé est laissée à la charge du développeur dans les classes concrètes.

14. Par addesse le 21/08/2009 08:55

merci beaucoup pour ce tuto assez clair,on aimerai plus de tuto sur les interfaces qui reste une notion assez difficile à métriser pour pas mal de débutant en POO. en tout cas Merci beaucoup pour le partage

15. Par Emacs le 21/08/2009 23:44

Le cours sur les interfaces est prévu, faut-il encore que je prenne le temps et la patience de l'écrire. Je vais essayer de me motiver dans les prochains jours