Ces précisions étant apportées, voici donc maintenant une première version des classes nécessaires et vous constaterez en l’exécutant avec le code suivant et une version de PHP ≥ 5.4 que le client a bien payé 30 avec une carte de crédit et dispose bien de 5 verres de Chimay Bleu remplis à 75 %.
$me = new client(); $barman = new barman(); $barman ->receiveOrder($me->order(5, 'Chimay bleu')) ->prepareOrder() ; $me->receiveDrinks($barman->giveDrinks()); $barman->billDrinks($me); $me->showDrinks();
Maintenant, voyons ce qu’il se passe lorsqu’on utilise ces classes avec le code suivant :
$me = new client(); $barman = new barman(); $barman ->receiveOrder($me->order(5, 'Chimay bleu')) ->prepareOrder() ; $me ->updateOrder(0, 'Chimay bleu') ->receiveDrinks($barman->giveDrinks()) ; $barman ->billDrinks($me) ->updateDrinks(); ; $me->showDrinks();
Vous constaterez que non seulement le client n’a rien payé, mais en plus, ses verres sont remplis à 10 % de 1664 !
Pourtant, il n’y a dans ce contexte aucune concurrence, aucune parallélisation, aucun fichier, aucune base de données, et toutes les propriétés des classes sont privées.
Je vous propose maintenant d’exécuter le code suivant avec ces autres classes :
$me = (new client())->order(5, 'Chimay bleu', $barman = new barman()); $barman ->prepareOrder() ->giveDrinks($me) ->billDrinks($me) ; $me->showDrinks();
Nous retrouvons le comportement de départ, puisque le client a bien payé 30 avec une carte de crédit et dispose bien de 5 verres de Chimay Bleu remplis à 75 %.
Et maintenant, je vous invite à essayer de faire en sorte avec ces classes que le client soit servi gratuitement dispose de verres remplis à 10 % de 1664 (sans toucher au code des méthodes existantes évidemment)…
Et pour ceux désirant des explications supplémentaires ou ne sont pas encore convaincu, je vous invite à lire ceci ou cela.
7 réactions
1 De Paul - 21/04/2014, 15:48
Dans le cas 1 vous avez des(une) méthodes pour vous tirer une balle dans le pieds "updateOrder"
Du coup pour moi c'est un comportement normal, vu que vous dites vous même "je veux modifier ma commande"
Ca revient au même que faire:
$a = 0;
// ...
$a = 1;
!! AHAH ICI $a = 1 alors que j'avais dis 0.!! BUUUUUUUG
Forcément si dans votre programme vous autorisez quelqu'un à modifier une commande, il faut pas s'étonner que ca ne marche pas...
Dans un tel système, une commande peut etre validée par le barman, annulée et c'est tout.
Si vous voulez d'autre verres, il faut faire une 2eme commande, ou annuler la 1ere et on en fera une 2eme (que ce soit transparent ou non pour le client).
Pour moi le problème reste le même vous voulez faire des opérations sur des objets que vous voulez être constant (état interne constant) et vous fournissez des moyens pour casser ce postulat.
Soit la commande est modifiable, et il faut définir les comportements souhaités, lorsqu'on modifie une commande déjà préparée, ou déjà servie ou même déjà payée.(et d'avoir un état de commande)
Soit la commande n'est pas modifiable et dans ce cas la il ne faut pas de modifieur.
Si c'est un environnement concurrent implémentation d'une section critique.
Ma version (je suis partie de la 1ere version) http://pastebin.com/JpcMqaGQ
Dans cette version je autorise le barman a traité(enregistrement de la commande, préparation, payment) qu'une commande à la fois.
Dans cette version les seuls liens sont entre le barman->client, barman->tpe et tpe->order
Et comme vous l'avez dis en utilisant des interfaces on résoudrait facilement ce problème.
PS Pour comparer avec votre mise en echec que la version 1:
$me = (new client())->order(5, 'Chimay bleu', $barman = new barman());
$barman->prepareOrder()->giveDrinks($me);
$me->order(0, '1644', $barman);
$barman->prepareOrder()
->billDrinks($me)
;
$me->showDrinks();
```
Mince le barman a oublier de facturer les bierres.
2 De mageekguy - 21/04/2014, 20:29
@Paul : Bien sur que le code de la première version est conçu pour ne pas fonctionner, puisque le but est de montrer que se baser sur un getter pour prendre une décision peut poser problème.
Difficile de le montrer sans le faire…
3 De Paul - 22/04/2014, 07:39
Donc vous êtes entrain de démontrer que ca marche pas, en faisant tout pour que ca ne marche pas ?
Avec cette méthode il est possible de montrer que tout peux poser problème.
.
Si vraiment ca ne marche pas, je pense qu'il faudrait mieux montrer que ca ne marche pas en faisant tout pour que ca marche
.
Montrez nous qu'en respectant tous les postulats de départ, que la version avec getter reste problématique ( je ne vois pas comment ca pourrait être le cas).
.
Je pense vous avoir montrer que si on implémentait les postulats de départ, alors la version 1 marche très bien et à contrario que votre version permet quand même d'avoir un résultat incohérent (le postulat non implémenté est "on ne peut pas faire une 2eme commande si on a pas payé la 1ere")
.
PS: dans ma version y a une typo dans $barman->giveDrinks() j'ai écris
$drinks = $this->order;
$this->drinks = null;
return $drinks;
au lieu de :
$drinks = $this->drinks;
$this->drinks = null;
return $drinks;
4 De mageekguy - 22/04/2014, 11:37
@Paul : On ne se comprend pas.
Bien sur que tu peux très bien avoir du code ayant exactement le même comportement avec les deux approches dans la plupart des cas (les exceptions étant les getters qui récupèrent systématiquement l'information demandée d'un fichier ou d'une base de données, voir d'un autre thread ou processus, auquel cas c'est au client de se prémunir de se comportement, sauf qu'il doit avoir connaissance de ce fait, donc connaître l'implémentation de la classe, ce qui va à l'encontre de l'encapsulation).
Et également évidemment qu'aucune des deux approches n'est "bug free" potentiellement.
Je cherche juste à montrer qu'en exposant des getter (ou des setter) qui ne servent à rien, on permet aux utilisateurs de la classe de s'en servir et donc de potentiellement générer des bugs (sans parler du fait que cela expose inutilement l'implémentation de la classe et complique donc à la fois son évolution et sa maintenance, car en plus de devoir modifier les méthodes qui définissent un comportement, le développeur devra obligatoirement assurer la compatibilité des getter/setter, mais c'est encore un autre débat).
Les bugs de la première version ne sont permis que par le fait que l'état des différentes instances peut être modifié, et de plus indépendamment du contrôle des instances censées les gérer.
5 De Paul - 22/04/2014, 13:14
Effectivement, je comprends mieux ce que vous vouliez dire.
Et on est bien d'accord trop de getter/setter ou des getters/setters inutiles permettront de faire une mauvaise utilisation de l'objet.
Parcontre la ou je ne suis pas trop d'accord c'est pour la phrase:
"""voir d'un autre thread ou processus, auquel cas c'est au client de se prémunir de se comportement, sauf qu'il doit avoir connaissance de ce fait, donc connaître l'implémentation de la classe, ce qui va à l'encontre de l'encapsulation"""
Non le client ne doit pas avoir connaissance de l'implémentation mais juste de connaitre l'environnement d'exécution.
De nos jours quasiment tous les système sont concurrents (utilisent un fichier sur un système multi-utilisateur et/ou une base de données, et/ou thread), donc on doit définir dès le départ comment gérer les cas concurrent
Dans le cas d'un site web en PHP, un cache SQL, tout faire dans une transaction, lire les fichiers en début de programme et c'est tout, etc...
Dans le cas d'un programme serveur en Java, utiliser les sémaphores/mutex/lock
Etc...
--
Question, est que le design pattern "factory" ne serait pas la solution permettrait de gérer la concurrence plus facilement (lock/mutex juste dans la factory) et d'avoir des classes les plus indépendantes possible ?
6 De bob l'éponge - 14/06/2014, 10:30
pareille pour la seconde itération :
À propos "de de" la programmation orientée objet #2
7 De mageekguy - 11/08/2014, 15:18
@bob l'éponge : Au moins je suis constant dans ma connerie…
Plus sérieusement, merci de m'avoir indiqué ces coquilles.