J'ai hâte de tomber sur un cas impossible en East, et la je remettrais en cause la méthodologie mais plus on creuse plus on voit que tout a une logique.
En East tu codes pas plus vite non, bien au contraire beaucoup plus lentement car ça t'impose de réfléchir a une vraie conception et architecture sinon tu vois tout de suite que ton code part en vrille rapidement. Comme avec mon précédent exemple de Beggar+Women
public function askGold(Beggar $beggar) {
$beggar->areYouAWomanByHero($this);
$beggar->areYouAKidByHero($this);
$beggar->areYouAJudokaByHero($this);
// ... Calcul de la somme en fonction de
// $this->beggarIsWoman, $beggarIsKid->isKid, $beggarIsJudoka->isJudoka
$thunes = ...;
$beggar->giveGold(new Gold($thunes))
}
}
Voilà. Et si tu crées une interface par composition, par exemple Hero/Kid alors je n'utiliserai jamais une de tes librairies !
C'est quoi la meilleure méthode pour toi alors ?
Il faut en fait déjà savoir si bon dit que Beggar est une femme parce que l'on veut qu'il puisse gérer les messages comme une Women ou parce qu'on veut cette information juste pour lui donner plus d'or. Si c'est juste pour lui donner plus d'or alors c'est une propriété privé de Beggar et elle n'implémente pas Women. Dans ce cas ma solution était bonne.
Et si on a vraiment besoin d'autant d'informations de Beggar pour prendre une décision il faut peut être penser le code autrement
05-06-2015, 06:15 PM (Modification du message : 05-06-2015, 06:55 PM par Xenos.)
Juste pour être sûr: ton exemple, niahoo, il est pas en East, puisque areYou* return un booléen? C'est pour illustrer le débat du instanceof?
Okay, un cas qui me semble impossible à résoudre sans instanceof, en voici un: Conan (classe) doit donner tout son or si et seulement si c'est une Women + Kid qui le lui demande. Si c'est un Kid seul, il donne 1 pièce, si c'est une Women seule il donne 2 pièces. Un Kid seul et une Women seule ne doivent pas pouvoir lui taxer plus d'or que ça.
Une classe Conan implémentant Hero, une classe Gamin implémentant Kid, une classe BBardot implémentant Women et une classe Gamine implémentant Kid et Women me semble être une bonne base de départ.
Voici un autre exemple qui, cette fois, me semble incodable en East: Conan doit donner son argent à une Women si et seulement si celle-ci est majeure. Women ne doit avoir aucun moyen de taxer de l'argent s'elle est mineure.
Même en supposant que Women ne puisse pas mentir sur son âge, ça va coincer...
05-06-2015, 06:40 PM (Modification du message : 05-06-2015, 06:44 PM par niahoo.)
(05-06-2015, 06:15 PM)Xenos a écrit : Juste pour être sûr: ton exemple, niahoo, il est pas en East, puisque areYou* return un booléen?
Pardon, mes fonctions sont mal nommées, on attend bien que le beggar appelle par derrière beggarIsAWoman par exemple, et les areYou sont en fait des "askIf".
(05-06-2015, 06:01 PM)srm a écrit : C'est quoi la meilleure méthode pour toi alors ?
Il faut en fait déjà savoir si bon dit que Beggar est une femme parce que l'on veut qu'il puisse gérer les messages comme une Women ou parce qu'on veut cette information juste pour lui donner plus d'or. Si c'est juste pour lui donner plus d'or alors c'est une propriété privé de Beggar et elle n'implémente pas Women. Dans ce cas ma solution était bonne.
Et si on a vraiment besoin d'autant d'informations de Beggar pour prendre une décision il faut peut être penser le code autrement
Pour moi la meilleure methode, je ne sais pas. Je n'essaie pas de voir si East fonctionne ou non, j'essaie de voir comment on peut le faire fonctionner sur le cas qu'à posé Xenos. Mais je suis débutant.
L'interface Beggar sert à demander des thunes. Donc si on lui demande si c'est une Woman dans ce cadre là, c'est clairement (amha) parce que ça influe sur le don de thunes. Et on veut clairement savoir si l'objet implémente Woman. Effectivement il y a un glissement sémantique car on se fout d'avoir une interface Woman ou non et donc on peut oublier les instanceof. Mais ça revient au même avec une propriété bool isWoman() des objets Beggar au fond.
ma proposition fonctionne tout de même.
De quelles solution de ta part parles-tu, qui fonctionnait ?
Je me suis renseigné concernant les interfaces composé etc.
En temps normal on ferait un type hint comme ça : Women|Beggar
PHP ne supporte pas encore ça mais visiblement ça va venir un peu plus tard.
Pour le premier cas Xenos
interface GiveGoldByHero
{
public function giveGoldByHero(Gold $gold, Hero $hero);
}
interface Kid extends GiveGoldByHero { }
interface Women extends GiveGoldByHero { }
interface WomenKid extends Women, Kid { }
interface Hero
{
public function askGoldByWomen(Women $women);
public function askGoldByKid(Kid $kid);
public function askGoldByWomenKid(WomenKid $womenKid);
}
final class Gold
{
private $value;
public function __construct($value)
{
if (is_integer($value) === false) {
throw new RuntimeException('Gold value should be construct from an integer');
}
if ($value < 0) {
throw new RuntimeException('Gold value should be greater than or equal to 0');
}
$this->value = $value;
}
public function getValue()
{
return $this->value;
}
}
class Conan implements Hero
{
private $gold;
public function __construct(Gold $gold)
{
$this->gold = $gold->getValue();
}
public function askGoldByWomen(Women $women)
{
$this->gold = $this->gold - 2;
$women->giveGoldByHero(new Gold(2), $this);
}
public function askGoldByKid(Kid $kid)
{
$this->gold = $this->gold - 1;
$kid->giveGoldByHero(new Gold(1), $this);
}
public function askGoldByWomenKid(WomenKid $womenKid)
{
$gold = $this->gold;
$this->gold = 0;
$womenKid->giveGoldByHero(new Gold($gold), $this);
}
}
class Gamin implements Kid
{
public function giveGoldByHero(Gold $gold, Hero $hero)
{
echo "Goood i can buy candies for " . $gold->getValue() . " gold\n";
}
}
class BBardot implements Women
{
public function giveGoldByHero(Gold $gold, Hero $hero)
{
echo "I can feed pet with food cost " . $gold->getValue() . " gold\n";
}
}
class Gamine implements WomenKid
{
public function giveGoldByHero(Gold $gold, Hero $hero)
{
echo "I can trick for " . $gold->getValue() . " gold\n";
}
}
$conan = new Conan(new Gold(300));
$gamin = new Gamin;
$bbardot = new Bbardot;
$gamine = new Gamine;
interface GiveGoldByHero
{
public function giveGoldByHero(Gold $gold, Hero $hero);
}
interface Kid extends GiveGoldByHero { }
interface Women extends GiveGoldByHero
{
public function ageIsAskedByHero(Hero $hero);
}
interface WomenKid extends Women, Kid { }
interface Hero
{
public function askGoldByWomen(Women $women);
public function askGoldByKid(Kid $kid);
public function askGoldByWomenKid(WomenKid $womenKid);
public function ageOfWomenIs(Age $age);
}
final class Age
{
private $value;
public function __construct($value)
{
if (is_integer($value) === false) {
throw new RuntimeException('Age value should be construct from an integer');
}
if ($value < 0) {
throw new RuntimeException('Age value should be greater than or equal to 0');
}
$this->value = $value;
}
public function getValue()
{
return $this->value;
}
public function executeIfGreaterOrEqualTo(callable $callable, self $age)
{
if ($this->value >= $age->value) {
$callable();
}
return $this;
}
}
final class Gold
{
private $value;
public function __construct($value)
{
if (is_integer($value) === false) {
throw new RuntimeException('Gold value should be construct from an integer');
}
if ($value < 0) {
throw new RuntimeException('Gold value should be greater than or equal to 0');
}
$this->value = $value;
}
public function getValue()
{
return $this->value;
}
}
public function ageOfWomenIs(Age $age)
{
$age->executeIfGreaterOrEqualTo(
function () { $this->giveGold = true; },
new Age(18)
);
}
}
class Gamin implements Kid
{
public function giveGoldByHero(Gold $gold, Hero $hero)
{
echo "Goood i can buy candies for " . $gold->getValue() . " gold\n";
}
}
class BBardot implements Women
{
private $age = 80;
public function giveGoldByHero(Gold $gold, Hero $hero)
{
echo "I can feed pet with food cost " . $gold->getValue() . " gold\n";
}
public function ageIsAskedByHero(Hero $hero)
{
$hero->ageOfWomenIs(new Age($this->age));
}
}
class Gamine implements WomenKid
{
private $age = 9;
public function giveGoldByHero(Gold $gold, Hero $hero)
{
echo "I can trick for " . $gold->getValue() . " gold\n";
}
public function ageIsAskedByHero(Hero $hero)
{
$hero->ageOfWomenIs(new Age($this->age));
}
}
$conan = new Conan(new Gold(300));
$gamin = new Gamin;
$bbardot = new Bbardot;
$gamine = new Gamine;
05-06-2015, 08:37 PM (Modification du message : 05-06-2015, 08:47 PM par Xenos.)
Déjà, beau travail
Par simplicité, je redonne les specs.
Note d'abord que, pour les deux cas, c'est hyper-verbeux alors que le cas n'est pas si complexe (tu trouves pas?)
Dans le premier cas (West, sans instanceof):
"Conan (classe) doit donner tout son or si et seulement si c'est une Women + Kid qui le lui demande. Si c'est un Kid seul, il donne 1 pièce, si c'est une Women seule il donne 2 pièces. Un Kid seul et une Women seule ne doivent pas pouvoir lui taxer plus d'or que ça."
• J'ai demandé "si j'implémente Kid et Women", pas "Si j'implémente WomenKid". Dans ta structure, une classe externe qui implémenterait Women et Kid serait rejetée car elle n'implément pas WomenKid. Pourtant, elle implément Women et Kid, comme je l'ai demandé. Les specs ne sont pas respectées.
• Si ma classe implémente Women+Kid, et qu'elle appelle askWomenByGold, pour moi, elle devrait recevoir l'argent d'une Women+Kid. Dans ton code, la classe appelante doit savoir qu'il y a une séparation de cas entre Kid, Women, et KidWomen.
• Je trouve dommage de stocker le $gold->getValue() dans Conan. Pourquoi pas le $gold directement?
• Si t'es forcé de passer par un WomenKid pour faire du Women+Kid, tu ne sens pas l'explosion combinatoire qui arrive? il faudra implémenter les interfaces AB pour chaque couple utile? ABC pour les triplets? etc? Et si tu crées ABC, il faudra l'implémenter dans tous les codes dont la classe est A,B,C ? Rien ne va te le signaler.
Dans le second cas (East, avec ou sans instanceof):
Conan doit donner son argent à une Women si et seulement si celle-ci est majeure. Women ne doit avoir aucun moyen de taxer de l'argent si elle est mineure.
• Heu, c'est pas du East: j'ai return $this->value; dans Age
• T'as pas l'impression que executeIfGreaterOrEqualTo sera copié-collé dans d'autres classes (comme Gold) ?
• ageOfWomenIs peut être appelé dans l'ordre qu'on veut:
Code PHP :
<?php class PetitePeste implements Women {
public function ageIsAskedByHero(Hero $hero) {
}
public function racketer(Hero $hero) { // Ca pourrait venir d'un autre bout de code:
// Dans un tel cas, bon courage pour débugger :) $hero->ageOfWomenIs(new Age(42));
$hero->askGoldByWomen($this);
}
}
Tu te retrouves avec le même genre de faille que les sites amateurs où un formulaire HTML appelle un script PHP, qui le vérifies puis redirige l'utilisateur vers admin.php: si on appelle admin.php directement, on saute tous les checks.
• ageOfWomenIs étant publique, elle peut également être appelée par n'importe quelle autre classe.
• Tu clones le héro, donc son or est infini. J'avais demandé à Conan de donner son argent, pas l'argent d'un clone
• Si, dans une autre méthode de Hero, il faut interroger l'age d'une Women, il faudra ajouter une méthode ageOfWomenIs, mais celle-ci existe déjà pour askGoldByWomen ?!