Un comble, non ?
Imaginez juste un instant la puissance d'un code tel que :
$facture = new facture();
foreach ($lignesFacture as $ligneFacture)
{
$facture += $ligneFacture;
}
echo $facture->total;
Vous voyez ou je veux en venir ?
Actuellement, pour faire une telle chose proprement, il faut faire
$facture = new facture();
foreach ($lignesFacture as $ligneFacture)
{
$facture->addLigne($ligneFacture);
}
echo $facture->total;
De mon point de vue, au niveau lisibilité du code, il n'y a pas photo.
Sans compter qu'il devient possible d'introduire de la généricité dans les algorithmes avec ce système, pour par exemple additioner n'importe quoi sans se soucier de la nature de ce qu'on additionne ;
$facture = new facture();
$elements = array(new ligneFacture(), new tva(), new client());
foreach ($elements as $element)
{
$facture += $element;
}
Autre paradoxe, s'il est possible de surcharger une méthode parente dans une classe fille, Il n'est pas possible de définir une méthode avec un même nom mais acceptant des arguments différents au sein d'une même classe.
C'est interdit. C'est pas bien. Ca tue les bébés phoques sur la banquise. Et puis d'abord, ca servirait à quoi, hein ?
Bonne question...
Actuellement, si une méthode doit effectuer un traitement différent en fonction des argument qu'elle reçoit, il y a deux stratégies possibles.
La première consiste à utiliser un code du type :
swtich (gettype($argument))
case 'int': ...
case 'array': ...
case 'stdClass': ...
Ainsi, la méthode sait
ce qu'elle doit faire en fonction du type de son argument.
Si cette stratégie est jouable pour des fonctions qui ne prennent qu'un argument, elle se transforme vite en chemin de croix à partir du moment ou le nombre d'arguments et de types possibles vont croissant.
La seconde stratégie consiste à définir plusieurs méthodes qui assurent la même fonction mais qui portent des noms différents, comme par exemple :
function insertFromArray(array $array) {...}
function insertFromObject(stdClass $object) {...}
Cette façon de faire à l'inconvénient d'obliger le programmeur à trouver des noms de méthodes cohérents et qui ressemble à quelque chose. Corollaire : le code devient truffé de méthodes portant un nom à rallonge qui font fondamentalement la même chose, et le développeur se prend la tête pour autre chose que le code métier
.
Encore une fois, rien de bien grave, hormis que :
- le code perd en lisibilité et devient donc moins facile à relire, maintenir, et corriger.
- le développeur perd la puissance de la généricité, puisque plus rien n'est générique.
- si un nouveau type doit être géré, le développeur doit se creuser le crâne pour trouver un nom de méthode qui ressemble à quelque chose et qui ne fasse pas 137 caractères de long.
En clair, nous sommes loin de la panacé, dans un cas comme dans l'autre.
La surcharge de méthode permettrait d'apporter une solution beaucoup plus élégante à cette problèmatique en permettant la définition de plusieurs méthodes de même nom mais disposant de signature différente.
Si PHP supportait vraiment la surcharge, les deux exemples de code précédent pourraient donc être remplacé avantageusement par le code suivant :
function insert(array $array) {...}
function insert(stdClass $objet) {...}
Avec cette solution, le code reste lisible, facile à comprendre, et il devient possible d'exploiter la puissance de la généricité sans aucun problème.
Et pour peu que le développeur mixe cela avec la surcharge d'opérateur exposée précédement, le paradis n'est plus très loin.
Mais pour l'instant, il est inacessible, à moins de se résoudre à modifier PHP lui même ou passer par une extension très peu répandue.
Cependant, comme je l'ai déjà dit, PHP est parfois capable d'évoluer dans le bon sens, et peut être qu'un jour il deviendra un langage qui supporte complétement le modèle objet.
Alors, j'ai encore un peu d'espoir...
5 réactions
1 De Eric - 26/11/2008, 12:38
Vrai et faux.
Déjà PHP accepte tout à fait la redéfinition de fonctions. Ce qu'il n'accepte pas - comme beaucoup de langages - c'est la surcharge des opérateurs, et uniquement ceux ci. Ca c'est pour la précision.
Maintenant ce n'est même pas tout à fait vrai. Certains opérateurs peuvent être réutilisés, c'est le cas de la notation de tableau.
Maintenant je reprend ton exemple :
$facture = new facture();
foreach ($lignesFacture as $ligneFacture)
{
$facture += $ligneFacture;
}
echo $facture->total;
Pourquoi un + ? Tu n'additionnes pas une facture et une ligne de facture, ce que tu fais c'est ajouter une ligne à une facture, qui est en fait une liste de lignes de facture.
La bonne syntaxe serait plutot en PHP : $facture[] = $ligneFacture ;
Et ... Justement, ça c'est faisable en PHP, $facture étant un objet et non un tableau simple.
Dans certains cas ça a du sens, ici franchement j'en doute. Le add*() me parait plus logique. En fait à ta facture tu n'ajoutes pas des lignes, tu ajoutes des produits, ou des services. Et là $facture->addProduit( $produit ) me semble beaucoup plus logique que ton idée à la base de $facture += $produit qui n'a AMHA pas beaucoup de sens (c'est additionner des choux et des carottes).
Si je puis me permettre, la surcharge d'opérateurs est justement exclue de beaucoup de langages justement pour ça : éviter que les développeurs en abusent et commencent à additionner des choux et des carottes au détriment de la lisibilité pour un tiers.
Parce que pour toi c'est peut être logique mais je t'assure que pour d'autres utiliser l'opérateur d'addition pour ajouter un élement à une facture, c'est tordu. L'ajout d'un élément à une liste (syntaxe $facture[] = $produit) est déjà limite dans ce cas là (parce que la facture n'est pas une simple aggrégation de produits, il y a d'autres composantes principales)
Après la seconde chose que PHP ne permet effectivement pas c'est une même méthode avec deux prototypes. Dans les faits c'est indispensable en java ou dans les langages typés statiquement, mais c'est plus un paliatif.
Il m'arrive d'en avoir besoin, et de faire une condition sur le type, mais c'est assez rare et c'est généralement limité à un tri entre le type natif ou l'objet ... et souvent pour convertir immédiatement l'un dans l'autre, pas pour faire deux traitements différents. En fait elle accepte la même données sous deux formes, une primitive (valeur ou identifiant) et une complète / objet.
Bref, une ligne suffit généralement. Le reste c'est du duck typing si jamais on doit accepter plein d'objets différents qui ont une même particularité.
Là aussi, si tu as une méthode qui acceptes des choux et des carottes, donc des objets qui n'ont rien à voir et sur lesquels même le duck typing ne va pas, c'est à 99% des cas que tu as mal conçu quelque chose ou que tu devrais de toutes façons faire deux méthodes même si PHP supportait plusieurs prototypes par méthode.
Le 1% restant peut tout à fait être réalisé via un wrapper générique qui contient un switch et une redirection vers 2 ou trois fonctions dédiées suivant les cas.
2 De Jérémy - 26/11/2008, 15:40
Je ne suis pas d'accord avec Eric.
La facture est un exemple.
On peut la remplacer par un objet mathematique real (composé d'un numerateur et un denominateur). les signes d'ajout (et autres multiplications) serait alors plusqu'indispensable !
<?
class real
{
public $nom = 0;
public $den = 0;
public function __construct($nom, $den)
{
$this->nom = $nom;
$this->den = $den;
$this->reduce();
}
public function add($real)
{
return new real(($this->nom * $real->den) + ($real->nom*$this->den), $real->den*$this->den);
}
public function reduce()
{
$p = $this->PGCD($this->nom, $this->den);
if($p!=0)
{
$this->nom /= $p;
$this->den /= $p;
}
if($this->nom<0 && $this->den<0)
{
$this->nom = 0-($this->nom);
$this->den = 0-($this->den);
}
}
private function PGCD($a, $b)
{
if($b==0) return $a;
$r = $a%$b;
return $this->PGCD($b, $r);
}
public function __tostring()
{
return $this->nom.'/'.$this->den;
}
public function __toFloat()
{
if($this->den == 0) return 0;
return $this->nom/$this->den;
}
}
?>
Et la aussi, j'aimerais pouvoir additionner autre chose que des objet de type real. ca peut etre un float, ou un int.
Les paramèters optionel, c'est sympa. Mais ca devien très rapidement bordelique...
Y'a qu'a voir les constructeur, qu'on peut appeler avec un objet de meme class en paramèter pour qu'il le clone, ou l'id d'une table pour qu'il le charge depuis la base de donnée, ou juste un string pour qu'il l'utilise comme "nom"... etc
NB : désolé pour l'indentation, je n'ai pas trouvé comme copier des portion de code sur le blog..
3 De Laurentj - 26/11/2008, 16:04
Je suis entièrement d'accord avec Eric. La surcharge des opérateurs parait sexy, mais devient vite un enfer. Pour ton exemple de code, je ne l'ai compris qu'en lisant l'équivalent classique que tu as indiqué. (et pourtant, j'en avais fait pas mal de la surcharge d'opérateur, je code beaucoup en C++)
En voyant ton $facture += $ligneFacture;, on peut se demander : est ce que tu ajoutes le montant de la ligne au total de la facture ou est ce que tu incorpores l'objet ligneFacture dans l'objet facture ? Tu vois, c'est très ambiguë (d'autant plus quand le développeur débarquant sur le projet n'est pas très au fait des rêgles de gestion, ou même des mécanismes de comptabilité). Et du code ambiguë, c'est difficilement maintenable, c'est lourd à lire, ça fait perdre du temps etc..
Quand à la définition multiple d'une méthode, je pense que Eric a tout dit. Ça peut apporter autant d'ambiguité à la lecture que l'utilisation de la surcharge des opérateurs.
4 De Eric - 27/11/2008, 10:34
Oui Jeremy, l'exemple des nombres a là du sens. Mais, combien de fois ça t'est arrivé sur une appli web en PHP d'avoir à faire ce genre de calculs ? et dans ces cas là combien de fois les bibliothèques mathématiques haute précision de PHP ne t'ont pas suffit ?
Soyons honnête, PHP n'est pas le plus adapté pour des calculs de recherche scientifique. Par contre pour un frontal web, une précision arbitraire (faisable en PHP) suffit largement.
Mais le plus souvent on manipule des chiffres beaucoup plus simple et le corps de PHP se compose d'objets fonctionnels. L'exemple de ta facture est bien plus réaliste que celui de tes nombres. Et dans ces cas là la surcharge d'opérateurs a beaucoup moins de pertinence. Ca sert parfois, généralement quasiment exclusivement pour les listes et ajout dans une liste. Parfois on pourrait aussi se servir des autres opérateurs de manière intelligente mais c'est assez rare et pas trop grave à faire autrement.
5 De Amaury - 23/01/2012, 00:11
Bon, j'arrive avec 3 ans de retard sur cet article, au détour d'un surf fortuit (merci Google).
Sur ce genre de question, il n'y a malheureusement pas de frontière nette. Dans certains cas, la surcharge semblera une réponse évidente, dans d'autres cas cela pourra être une mauvaise idée. Les cas fluctuent en fonction des projets, du code, des objets... et des codeurs.
Pour reprendre l'exemple initial (la surcharge d'opérateur sur l'objet Facture), j'aurais tendance à apprécier ce genre de possibilité - si elle existait en PHP - à la condition unique de ne l'autoriser qu'en restant sur le même type. Bref, additionner une facture à une autre, pour obtenir une autre facture qui contient les informations des deux, pourquoi pas. Pour les cas problématiques (les deux factures sont destinées à deux personnes différentes ; comment choisir ?), on lève une exception.
Par contre, additionner une facture avec un objet d'un autre type, non. Ça pourrait être une "ligne de facture" à ajouter à la liste des lignes de la facture ; ou un nombre (à ajouter au total ?) ; ou une chaîne de caractères (à ajouter comme nom du facturé ?)... Il est impossible de savoir à l'avance comment un développeur interprétera le signe "+".
On en revient aux fondamentaux qui font que certains (dont moi) aiment le C pour sa simplicité : des variables pour les données et des fonctions pour les manipuler. C'est plus verbeux à lire, c'est sûr. Mais sans aucune ambiguïté.