JeuWeb - Crée ton jeu par navigateur
Compass : East Oriented - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Compass : East Oriented (/showthread.php?tid=7165)

Pages : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30


RE: Compass : East Oriented - srm - 04-06-2015

(04-06-2015, 11:59 AM)Xenos a écrit : Si le setter est un ordre donné à la classe, on n'est pas dans le je te fais confiance. Sinon, le setBroken doit aussi être testé dans le contexte présente (aka, le test précédent teste à la fois setBroken et userWantsToWatchYou).
C'est pas exactement un ordre en fait. C'est une initialisation. Et je lui fais confiance pour s'initialiser correctement.
Si c'était un comportement je lui aurais dis "je veux te casser" et la télé aurait pu me dire "je suis incassable" et ne rien faire, ou se casser.

(04-06-2015, 11:59 AM)Xenos a écrit : Pourquoi le fait que la classe appelée fasse le tri Barman/Policier ne serait pas Open/Close?
Okay, le BarmanPolicier en East peut se présenter comme l'un ou comme l'autre. Et s'il veut se présenter comme les deux? Perso, je dirai que cela rajouterait une interface (BarmanAndPoliceman), mais là encore on effondre la combinatoire.

Parce que tu dois modifier du code existant pour rajouter des instanceof
Si quelqu'un est à la fois un Barman et un Policier et que l'on a besoin de ça... Je t'avoue que je vois pas trop de cas concret et que je ne sais pas trop comment on résoudrait ça proprement.
Mais peut-être avec une autre interface qui est la composition des deux.

(04-06-2015, 11:59 AM)Xenos a écrit : Si getReligion est déjà dans Consumer (ça aurait plus sa place dans Human d'ailleurs), il n'y a rien à changer à l'interface en West. C'est un cas probable, puisque le getReligion est commun à plusieurs (toutes) interfaces appelantes. En revanche, en East, il faudra forcément la changer puisqu'il n'y a aucune chance que Barman ait déjà son askReligionByBarman. C'est le soucis de réutilisation introduis par les askWhatByWho.

Le soucis et l'avantage de la souplesse et des modifications open/close, puisqu'au moins je peux lui répondre ce que je veux au Barman alors qu'avec un getter je ne peux pas.

(04-06-2015, 11:59 AM)Xenos a écrit : Si Beaucoup plus de classes tierces impactées et encore heureux., alors ton code est tentaculeux, et le moindre changement va impliquer des modifications partout, ce qui va rendre la maintenance affreuse. L'interface n'a pas vocation à changer (Open/Close principle), et East oblige à modifier 2 interfaces pour inclure la religion, alors que West n'en modifie au pire qu'une (getReligion, et encore on peut s'en passer en créant de nouvelles interfaces, une nouvelle classe et le pattern Decorator/la composition) voire, 0 (c'est le code interne qui change).

Partout faut le dire vite, il y a pas 50 classes qui dépendent d'une interface.
Et quoi qu'il en soit, ça a toujours été le principe des interfaces.
Montre voir ce que ça donnerait avec ton decorator et composition, ça sera plus facile ensuite pour pointer les soucis que ça provoque cette solution.


RE: Compass : East Oriented - Xenos - 04-06-2015

Citation :Le soucis et l'avantage de la souplesse et des modifications open/close, puisqu'au moins je peux lui répondre ce que je veux au Barman alors qu'avec un getter je ne peux pas.
Je n'ai pas compris la phrase :\ Je re-soulève le point que PHP n'a pas de typage de retour (les type hintings sont tous facultatifs).

Peut-être pas 50 classes, okay, mais plusieurs, ce qui veut dire que les changements devront être fait à d'autres endroits du code que là où le changement est demandé. Faudrait, pour éviter cela, que East permette d'avoir une approche d'ajout d'interfaces plutôt que d'édition d'interfaces.


Supposons que getReligion n'existe pas pour le consommateur, et que le barman veuille la prendre en compte pour savoir s'il sert ou non. Je vais élaguer le code d'exemple.


Situation actuelle (West):

Code PHP :
<?php 
interface IAlcoholConsummer {
public
askAge();
}

interface
IBarman {
public
giveAlcoholTo(IAlcoholConsummer $consummer);
}

class
Client implements IAlcoholConsummer {
public
askAge() {
// histoire de montrer que c'est pas forcément un "getter"
return (new Date(Date.now() - $this->birthday)).asYear();
}
}

class
Barman implements IBarman {
public
giveAlcoholTo(IAlcoholConsummer $consummer) {
// Note qu'on présume que askAge renvoie un age en année,
// Il serait plus pertinent de ne pas présumer puisque ce n'est pas dans le contrat d'interface
return ($consummer->askAge() > $this->minAge) ? new Alcohol() : null;
// ou Alcohol.NULL dans le cadre du null-object pattern / typehinting du retour
}
}


Ajout de la religion avec modification d'interface:

Code PHP :
<?php 
interface IAlcoholConsummer {
public
askAge();
// L'interface est modifiée
public askReligion();
}

interface
IBarman {
public
giveAlcoholTo(IAlcoholConsummer $consummer);
}

class
Client implements IAlcoholConsummer {
public
askAge() {
return (new
Date(Date.now() - $this->birthday)).asYear();
}

// La classe Client est modifiée
public askReligion() {
return
$this->religion; // ou clone $this->religion
// on en revient à un des soucis des accesseurs que j'avais posé il y a bien 1 an
}
}

class
Barman implements IBarman {
public
giveAlcoholTo(IAlcoholConsummer $consummer) {
return (
$consummer->askAge() > $this->minAge
&& in_array($consummer->askReligion(), $this->allowedReligions)
) ? new
Alcohol() : null;
}
}

Là, on a changé l'interface IAlcoholConsummer: il faut donc modifier toutes les classes qui l'implémentent. Si les interfaces sont partagées entre plusieurs projets (par dépendances), cela peut vite coincer.



Ajout sans changement d'interface ni de classe:

Code PHP :
<?php 
// pas de changement
interface IAlcoholConsummer {
public
askAge();
}

interface
IBarman {
public
giveAlcoholTo(IAlcoholConsummer $consummer);
}

class
Client implements IAlcoholConsummer {
public
askAge() {
return (new
Date(Date.now() - $this->birthday)).asYear();
}
}

// Nouvelle interface
interface IReligousThing {
public
askReligion();
}

// On peut créer une classe juste pour la partie "religious", et la composer comme Client
class ReligiousClient implements IReligousThing, IAlcoholConsummer {
private
$consummer;
private
$religion;
public function
__construct(IAlcoholConsummer $consummer, $religion) {
$this->consummer = $consummer;
$this->religion = $religion; // non-typé, mais typable
}
public function
askAge() {
return
$this->consummer->askAge();
}
public function
askReligion() {
return
$this->religion;
}
}

class
Barman implements IBarman {
// Aucun changement dans le contrat de la méthode
public giveAlcoholTo(IAlcoholConsummer $consummer) {
return (
$consummer->askAge() > $this->minAge
&& (!$consummer instanceof IReligousThing
|| in_array($consummer->getReligion(), $this->allowedReligions)
? new
Alcohol() : null;
}
}
Là, les interfaces ne changent pas, et rien dans le reste du code ne sera impacté.

Si la méthode giveAlcoholTo était plus complexe, on aurait pu faire autrement, sans toucher au code interne de cette méthode complexe:

Avant:
Code PHP :
<?php 
class Barman implements IBarman {
public
giveAlcoholTo(IAlcoholConsummer $consummer) {
/* Complex conditions returning either new Alcohol() or null */
}
}

Après:
Code PHP :
<?php 
class Barman implements IBarman {
// La méthode complexe est renommée et masquée en interne
private giveAlcoholToConsummer(IAlcoholConsummer $consummer) {
/* Complex conditions returning either new Alcohol() or null */
}

// La méthode publique (100% nouvelle)
public giveAlcoholTo(IAlcoholConsummer $consummer) {
return (
!
$consummer instanceof IReligousThing
|| in_array($consummer->getReligion(), $this->allowedReligions)
) ?
$this->giveAlcoholToConsummer($consummer) : null;
}
}



RE: Compass : East Oriented - srm - 04-06-2015

(04-06-2015, 01:25 PM)Xenos a écrit :
Citation :Le soucis et l'avantage de la souplesse et des modifications open/close, puisqu'au moins je peux lui répondre ce que je veux au Barman alors qu'avec un getter je ne peux pas.
Je n'ai pas compris la phrase :\ Je re-soulève le point que PHP n'a pas de typage de retour (les type hintings sont tous facultatifs).
C'est bien ce que je dis, c'est un soucis certes dans une certaine mesure, mais cette méthode à d'avantage de souplesse et vu que j'ai askAgeByBarman($barman) je peux très bien faire : $barman->userAgeIs($this->age + 30);
Donc mentir à un Barman mais si j'ai askAgeByStats là je vais répondre $this->age.
Avec getAge() je ne peux pas faire ce genre de choses. Je ne suis pas souple.

Peut-être pas 50 classes, okay, mais plusieurs, ce qui veut dire que les changements devront être fait à d'autres endroits du code que là où le changement est demandé. Faudrait, pour éviter cela, que East permette d'avoir une approche d'ajout d'interfaces plutôt que d'édition d'interfaces.

Dans ton exemple tu montres toi même deux problème avec ta méthode.
// Note qu'on présume que askAge renvoie un age en année,
// Il serait plus pertinent de ne pas présumer puisque ce n'est pas dans le contrat d'interface

Ca c'est un soucis majeur.
Tu as des interfaces mais tu ne peux pas te reposer dessus pour avoir un code fiable alors que c'est leur but et principe.

Et que tu nommes askAge ou getAge tu est obligé de faire le même type d'action quelque soit l'objet qui le demande.
Tu n'as pas de soupless et d'extensibilité possible selon ne type d'objet qui te demande l'information.

Tu dois modifier toutes les classes qui implémente l'interface oui.
C'est là que le semver devient important.
Et qui fait que justement ça coincera pas. Mais respecter le semver n'a pas de rapport avec East en soit.
C'est juste utiliser des bonnes pratiques, le problème que tu soulèves se pose aussi quand tu ne fais pas du East.

Et c'est parfaitement logique, de toujours respecter les interfaces et n'utiliser que ce qu'elles définissent permet d'éviter pas mal d'erreurs masquées.

La méthode de composition que tu proposes, tu peux la faire aussi en East, mais tu as moins de souplesse si tu fais comme ça.
Si tu veux avoir comportement askAge différent si la personne qui te le demande est religieuse ou non, tu es bloquée.
Ou alors tu vas devoir ajouter des instanceof. Donc ne pas respecter l'open/close principe et en plus tu vas devoir connaître l'objet extérieur avec lequel tu travails, donc une dépendance plus forte.


RE: Compass : East Oriented - niahoo - 04-06-2015

$tv->smashedByBaseballBat() ?

Bon par contre je suis en train de regarder l'exemple de phpTour2015 et c'est l'exact opposé de ce que j'aime faire en programmation.

Absolument tout repose sur des contre-contre-appels de fonctions avec une modification de $this implicite pour dire "le client en cours a l'age de boire". J'aime bien l'idée de East mais l'implémentation donnée en exemple est vraiment bof.


RE: Compass : East Oriented - Xenos - 04-06-2015

Citation :Avec getAge() je ne peux pas faire ce genre de choses. Je ne suis pas souple
Je te le redis: oui, tu le peux (à condition de passer l'appelant en paramètre du getAge; sinon, ça repose sur la stacktrace, et c'est plutôt lent), et c'est la responsabilité de la classe appelée (et non de l'appelante) qui est engagée (via instanceof ou n'importe quel autre mécanique interne).

Exemple:
Code PHP :
<?php 
class Client implements IClient {
private
$combienDeFoisLINSEEMaDemande = 0;
public function
getAge($caller) {
// discriminer via instanceof
// ou via n'importe quel autre critère
// comme le nombre de sollicitations de l'INSEE
}
}

class
Barman {
//...
$client->getAge($this);
}

D'ailleurs, en East, tu exposes le fait que le Client peut répondre différemment suivant si un Barman, un Policier, ou l'INSEE demande l'age. Tu en sais finalement plus que tu ne le crois sur cet appelé. En approche classique, t'en sais rien. Si cela se trouve, l'appelé ignorera complètement le typage de l'appelant, ou alors il se basera sur des critères externes à l'appelant, etc.

Rien n'oblige l'appelé à faire le "même type d'action quelque soit l'appeleur". Si l'appeleur n'est pas passé en paramètre, l'appelé n'a pas connaissance de qui il est, donc c'est normal qu'il ne puisse pas réagir différemment (l'appelé ne connait pas le contexte appelant). Si l'appeleur est passé en paramètre, l'appelé peut réagir comme il le souhaite.


Pour moi, y'a clairement une différence entre East (return $this ou return null; obligatoire) et le fait de passer ou non des paramètres additionnels aux getters (qui demande la donnée par exemple).



Attends, là, le problème du askAge peut retourner ce qu'il veut, c'est inhérent au langage PHP, et c'est même ce que tu en attends quand tu dis East permet de "retourner" ce que je veux: null, un faux age, ou pas un age.
Il sera en plus possible de le typehinter en version 7; donc si l'argument est East c'est bien car je peux 'typer' mes retours, il sera invalide en PHP7 (et East comblerait alors une lacune du langage: c'est un paradigme pour faire un langage, pas pour l'utiliser).
De plus, en un sens, on s'en "fiche" de ce qui est retourné, puisque ce que l'on veut, c'est le comparer à $this->minAge. Si tu veux blinder tes codes, tu peux toujours faire
Code PHP :
<?php 
$clientRawAge
= $client->askAge();
$clientAge = (int)$clientRawAge; // On peut aussi tester instanceof TimeInterval qui serait un valueobject
return $clientAge > $this->minAge &&...

Si t'as besoin de connaitre l'appeleur, en West comme en East, tu devras le passer à la méthode appelée, y'a pas de dépendance "plus forte" en West de ce point de vue-là. Et l'Open/Close principle est bien respecté dans l'exemple West que je t'ai donné, puisque je n'ai ni changé les interfaces/classes existantes (autre que Barman), ni touché au contenu de la méthode complexe.


RE: Compass : East Oriented - srm - 04-06-2015

(04-06-2015, 03:29 PM)niahoo a écrit : $tv->smashedByBaseballBat() ?

Bon par contre je suis en train de regarder l'exemple de phpTour2015 et c'est l'exact opposé de ce que j'aime faire en programmation.

Absolument tout repose sur des contre-contre-appels de fonctions avec une modification de $this implicite pour dire "le client en cours a l'age de boire". J'aime bien l'idée de East mais l'implémentation donnée en exemple est vraiment bof.

Qu'est ce qui t'embête en soit ?


RE: Compass : East Oriented - srm - 04-06-2015

(04-06-2015, 04:42 PM)Xenos a écrit :
Citation :Avec getAge() je ne peux pas faire ce genre de choses. Je ne suis pas souple
Je te le redis: oui, tu le peux (à condition de passer l'appelant en paramètre du getAge; sinon, ça repose sur la stacktrace, et c'est plutôt lent), et c'est la responsabilité de la classe appelée (et non de l'appelante) qui est engagée (via instanceof ou n'importe quel autre mécanique interne).

Exemple:
Code PHP :
<?php 
class Client implements IClient {
   private $combienDeFoisLINSEEMaDemande = 0;
   public function getAge($caller) {
       // discriminer via instanceof
       // ou via n'importe quel autre critère
       // comme le nombre de sollicitations de l'INSEE
   }
}

class
Barman {
   //...
   $client->getAge($this);
}

Dans cet exemple tu ne peux RIEN faire avec $caller.
Sauf si tu t'amuses à faire des "$caller instanceof", mon dieu... Ca t'impose de connaître le monde extérieur et plus que toi.
Si j'ajoute une nouvelle classe, je dois rajouter un cas instanceof, mon objet Client doit donc avoir connaissance de tout le monde extérieur. Nettement pire qu'avec une interface ou ça va casser directement et tu vas le voir, là tu ne le verra pas.

(04-06-2015, 04:42 PM)Xenos a écrit : D'ailleurs, en East, tu exposes le fait que le Client peut répondre différemment suivant si un Barman, un Policier, ou l'INSEE demande l'age. Tu en sais finalement plus que tu ne le crois sur cet appelé. En approche classique, t'en sais rien. Si cela se trouve, l'appelé ignorera complètement le typage de l'appelant, ou alors il se basera sur des critères externes à l'appelant, etc.
Tu sais quel type d'objet sait et à quel type de message il sait répondre, ce qui est définis par un contrat (l'interface).
Ton objet ne doit donc pas deviner le monde extérieur. Et n'a donc aucune dépendance à l'extérieur. Puisque tout ce qu'il doit connaitre est définis dans sa classe à lui. Quand on a getAgeByBarman(Barman $age) ton objet à connaissance qu'il va travailler avec un Barman, tout est bien déterminé.

(04-06-2015, 04:42 PM)Xenos a écrit : Attends, là, le problème du askAge peut retourner ce qu'il veut, c'est inhérent au langage PHP, et c'est même ce que tu en attends quand tu dis East permet de "retourner" ce que je veux: null, un faux age, ou pas un age.
Non pas du tout, en East si il décide de donner son âge (déjà il peut décider de ne pas le faire) dans tous les cas il devra le donner au format déterminé par le receveur. Donc si le receveur n'autorise pas null, tu ne pourras pas donner null.

(04-06-2015, 04:42 PM)Xenos a écrit : Il sera en plus possible de le typehinter en version 7; donc si l'argument est East c'est bien car je peux 'typer' mes retours, il sera invalide en PHP7 (et East comblerait alors une lacune du langage: c'est un paradigme pour faire un langage, pas pour l'utiliser).
Encore une fois, il ne s'agit pas de retourner des valeurs, ou de typer les retours. Mais de demander à l'utilisateur de communiquer une donnée au format choisi par le receveur. C'est pas l'utilisateur qui choisi.


(04-06-2015, 04:42 PM)Xenos a écrit : De plus, en un sens, on s'en "fiche" de ce qui est retourné, puisque ce que l'on veut, c'est le comparer à $this->minAge. Si tu veux blinder tes codes, tu peux toujours faire
Code PHP :
<?php 
$clientRawAge
= $client->askAge();
$clientAge = (int)$clientRawAge; // On peut aussi tester instanceof TimeInterval qui serait un valueobject
return $clientAge > $this->minAge &&...
Si askAge() te retourne un objet, tu vas pas aller loin avec (int).
Tu vas me dire "oui je fais donc un instanceof", donc je vais te dire "oui donc ton objet doit connaitre le monde extérieur"
Et donc tu codes plus ta classe uniquement en fonction de celle-ci, mais en fonction de l'univers ou elle fonctionne. Ton code est plus difficile à maintenir du coup.


RE: Compass : East Oriented - Xenos - 04-06-2015

instanceof ne t'impose en rien de connaitre le monde extérieur: c'est un opérateur, et au même titre que tout autre opérateur, tu peux le lire instanceof($object, $instanceName) au lieu de $object instanceof $instanceName si tu préfères. Je ne vois pas en quoi cela te gène.
Ajouter une classe ne casse en rien le code existant, puisque le typehinting t'assures que le paramètre implémente telle interface. Que le paramètre soit une nouvelle classe implémentant d'autres interfaces, tu t'en fiches complètement. Donnes-moi un cas concret avec du code où cette histoire d'instanceof gènerait. Donne-moi le cas-code où l'ajout de la classe va détruire mon code et mes instanceof.

Montre-moi 1) en quoi instanceof c'est "connaitre l'univers qui m'entoure", et 2) un code où ajouter des classes et interfaces comme précédemment casserait tout sans que je ne le vois.


RE: Compass : East Oriented - srm - 04-06-2015


class Bouh
{
public function hello($data)
{

}
}
Tu mets quoi comme instanceof dans ce code ? Smile


class Bouh
{
public function hello(Data $data)
{

}
}
Hop, aucun soucis tu as toutes les infos qu'il faut pour travailler.


RE: Compass : East Oriented - Xenos - 04-06-2015

Tu mets les instanceof dont la méthode a besoin pour faire son boulot.
Dans le 2nd cas, tu sais que tu bosses avec un truc implémentant obligatoirement l'interface Data. Mais ce "truc" peut implémenter d'autres interfaces. Tu as le droit d'interroger ce $data pour lui demander si c'est un Age ($data instanceof Age) et d'agir en conséquence.
Dans le 1er cas, pareil, sauf que tu bosses avec un $data encore plus large (c'est "un truc quelconque").

Tu confonds la signature de la méthode, qui dit "Je n'accepte ce message que si tel paramètre implémente telle interface", sa documentation qui dit voici ce que je fais, et son algorithme interne qui est voilà comment je le fais. Cet algorithme est caché au monde extérieur (et c'est bien). Qu'il se base sur le fait que tel paramètre est de tel type, que tel paramètre est de tel autre type et renvoie tel autre résultat, de l'extérieur, on s'en fiche.

Ma boîte est en fait tellement noir que l'extérieur ne sait même pas qu'elle fait une discrimination en fonction des interfaces de ses paramètres. Et dans le 1er cas, elle connait encore moins de choses sur l'extérieur (puisqu'elle doit interroger cet extérieur à coup d'instanceof).