JeuWeb - Crée ton jeu par navigateur
Valueobject - 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 : Valueobject (/showthread.php?tid=7392)

Pages : 1 2 3 4 5 6 7 8


RE: Valueobject - Max72 - 05-09-2015

Aargh, tu me fais bosser le samedi matin ^^

Egalité des attributs

Si le VO ne représente pas un simple nombre ou chaine (comme des € vs $ ), alors ça change la donne et pour chaque 'devise', il faut un VO.
Voilà comment j'implémenterai ton exemple :

Code PHP :
<?php 
interface ValueObjectInterface

{
   public function getNative();
   
   public
function __toString();
   
   public
function isEqual(ValueObjectInterface $value);
}

class
Real implements ValueObjectInterface
{
   protected $value;
   
   public
function __construct($value)
   {
       $value = filter_var($value, FILTER_VALIDATE_FLOAT);
       if ($value === false)
       {
           throw new InvalidArgumentException('value doit etre un nombre réel');
       }
       $this->value = $value;
   }
   
   public
function getNative()
   {
       return $this->value;
   }
   
   public
function __toString()
   {
       return strval($this->value);
   }
   
   public
function isEqual(ValueObjectInterface $value)
   {
       if (!$value instanceof $this)
       {
           return false;
       }
       
       return
($this->getNative() === $value->getNative());
   }
}

abstract class
Temperature extends Real
{
   abstract public function toCelsius();
   abstract public function toFahrenheit();
}

class
Celsius extends Temperature
{
   public function toCelsius()
   {
       return new Celsius($this->value);
   }
   
   public
function toFahrenheit()
   {
       return new Fahrenheit($this->value * 1.8 + 32);
   }
}

class
Fahrenheit extends Temperature
{
   public function toCelsius()
   {
       return new Celsius(($this->value - 32) /1.8);
   }
   
   public
function toFahrenheit()
   {
       return new Fahrenheit($this->value);
   }
}

$cel = new Celsius(100);
$fah = new Fahrenheit(100);

echo
$fah->toCelsius()->getNative();
// 37.777777777778

var_dump($fah->isEqual($cel));
// bool(false)

$other_fah = new Fahrenheit(212);

var_dump($other_fah->toCelsius()->isEqual($cel));
// bool(true)

J'ai mis ça sur un Gist si vous voulez : https://gist.github.com/Max-koder/75c8dffb511ca575c2da

Ici, si tu essayes de comparer 2 températures qui n'ont pas la même devise (je ne sais pas comment on dit ^^ ), ça te retourne false, logique.
Il faut passer par une conversion.
Bien entendu, j'aurai très bien pu mettre dans ma class Température des tests pour comparer automatiquement ces températures (avec des if instanceof Celsius , if instanceof Fahrenheit , mais il est tôt ^^ ).

Edit : Si tu n'es pas convaincu par cette méthode, j'en ai une autre en réserve :p

Mutable
Si tu as besoin de le muter, modifier, avoir un tas de méthode, c'est que tu n'as pas besoin d'un VO, mais d'un objet tout court.


L'article VOvsDTO
Pour moi, l'Adresse du perso est bien un VO, car je l'ai créé comme tel. Pas de setter, pas de méthode excepté getter/comparaison/constructor.
J'aurai très bien pu créer une classe comme Personnage et mettre des setters pour y modifier la rue etc, mais je ne l'ai pas fait, ce qui rend par définition ma classe

Adresse un Value Object Smile

Enfin, oui, un DTO est mutable, c'est une des différences avec le VO. Une autre, c'est que tu ne le check pas (test pour savoir si c'est bien un INT etc). Enfin (toujours selon moi), il ne possède pas d'autre méthode que constructor, getter & setter.


RE: Valueobject - Xenos - 05-09-2015

Je ne suis effectivement pas convaincu car, mis à part le fait que j'aurai utilisé une interface Temperature et non une classe abstraite, il faut faire la conversion à la main dans le code utilisateur.
De plus, puisque tu parles de monnaie, comment vas-tu faire pour implémenter un système où la monnaie est une donnée? Par exemple, si les joueurs peuvent créer leur propre monnaie dans le jeu, tu ne peux pas avoir 1 classe par monnaie: il te faudra forcément une classe générique à toutes les monnaies. Du coup, si j'ai {100;€} et {130;$}, comment savoir si cela représente la même somme d'argent?

Citation :Si tu as besoin de le muter, modifier, avoir un tas de méthode, c'est que tu n'as pas besoin d'un VO, mais d'un objet tout court.
C'est vrai. Donc un ValueObject peut évoluer en Object normal?

Je suis d'accord avec la définition du DTO. La définition de base "Une classe est un ValueObject si et seulement si deux instances différentes A et B de cette classe peuvent être égales" (peu importe la condition d'égalité) me convient. Le caractère Immutable, à la limite bon. Mais "toCelsius" et "toFahrenheit" (voire "toKelvin", "toReaumur", "toLeiden"...), ce ne sont pas des comportements?

Demandé autrement: quelle différence entre DTO Immutable, ValueObject, et Object?


RE: Valueobject - Max72 - 05-09-2015

Décidément, tu essayes de me pousser dans mes retranchements Smile

Citation :Je ne suis effectivement pas convaincu car, mis à part le fait que j'aurai utilisé une interface Temperature et non une classe abstraite, il faut faire la conversion à la main dans le code utilisateur.
De plus, puisque tu parles de monnaie, comment vas-tu faire pour implémenter un système où la monnaie est une donnée? Par exemple, si les joueurs peuvent créer leur propre monnaie dans le jeu, tu ne peux pas avoir 1 classe par monnaie: il te faudra forcément une classe générique à toutes les monnaies. Du coup, si j'ai {100;€} et {130;$}, comment savoir si cela représente la même somme d'argent?

Voilà un autre code qui, je l'espère répondra à tes questions : https://gist.github.com/Max-koder/04d8a4515cdb4173503e
- J'ai à présent 2 interfaces et une classe abstraite
- Plus de conversion à la main dans le code utilisateur

L'idée est de convertir toutes les devises en dollars lors de la récupération par 'getUniformDevise()'. Je te laisse critiquer Wink

Je ne saisi pas la suite. La monnaie est créée par l'utilisateur (donc une valeur non définie dans le code) ? Si c'est cela, j'implémenterai une classe générique comme ma RealDevise avec une propriété $taux_de_conversion, qui serait récupérée depuis ma BDD. Ensuite une petite conversion, et on arrive facilement à voir si 2 monnaies se valent ou pas. En fait, je ne comprends vraiment pas ton problème ^^

Citation :C'est vrai. Donc un ValueObject peut évoluer en Object normal?
Je n'ai pas dit ça. Tu as simplement besoin d'un objet, c'est tout. Si tu prends un Value Object très simple (comme ici : https://gist.github.com/Max-koder/224c1488db1e05c54755 ), tu lui ajoute des setters et d'autres méthodes, ce n'est plus un VO, mais un objet classique.
Imaginons que tu achètes une moto. Tu lui enlèves le guidon, met un volant, l'élargit, lui rajoute une carrosserie et pose 2 roues supplémentaires. C'est toujours une moto ? Pas pour moi.

Citation :Mais "toCelsius" et "toFahrenheit" (voire "toKelvin", "toReaumur", "toLeiden"...), ce ne sont pas des comportements?
Si. Mais je n'ai jamais dit qu'un VO ne devait pas possèder de comportement, contrairement aux DTO d'ailleurs.

Citation :quelle différence entre DTO Immutable, ValueObject, et Object?
Ca sent la conclusion ça ^^

DTO Immutable : Un DTO n'est pour moi pas immutable, mais bon.
Simple conteneur de données, sans aucun check ni comportement. On lui donne une valeur, on peut la modifier, la récupérer, point.

ValueObject : Immutable, comparaison basée sur la/les attributs et non sur l'identité.

Objet : Tout ce que permet un objet (statique, dynamique, getters, setters, .............).


RE: Valueobject - Xenos - 06-09-2015

Je pousse toujours dans les retranchements, car mieux vaut y tomber tôt que tard, une fois le projet avancé Smile


echo $eur->getUniformDevise() . ' $';
J'éviterai, puisque cela mélange la "monnaie virtuelle" (UniformDevise) et la monnaie réelle Dollars. D'ailleurs, comment tu convertis Dollars en Euros ? Pour moi, s'il y a monnaie virtuelle, chaque monnaie réelle doit accepter cette monnaie virtuelle en paramètre de son constructeur (et non un entier). Ou mieux, accepter n'importe quelle Monnaie et en récupérer la valeur virtuelle pour la convertir.

RealDevise ne me semblait pas réalisable avant, sans cette notion de "UniformDevise".


La définition de DTO ok (je suis d'accord que rien ne l'oblige à être immutable, mais si on le veut immutable on le peut).
Celle d'objet, ok.
Celle de VO implique donc que je peux mettre tous les comportements de la Terre que je veux: un Objet avec un tas de comportement, mais qui serait immutable et dont deux instances distinctes peuvent être égale serait donc un VO?

Citation :Le value object [permet] la manipulation d’objet dont le comportement est bien défini, au lieu de tableaux associatifs impossibles à tester
Un tableau associatif est mutable et sans comportement, c'est plutôt un DTO qu'un VO.


Du coup, j'essaie de classer les types de... classes :

[Image: ValueObjectAndDTO.png]

Là, pour toi les VO sont la partie IE (Immutable, Equality; qui inclut BIE) et les DTO la zone BE (Behaviorless, Equality; BIE inclus aussi). Donc un DTO mutable n'est pas un VO (mais un DTO Immutable est un sous-genre de VO)?
Du coup, si deux objets partagent un même VO (par exemple, Alice et Bob partagent Adresse={1, MainStreet, NewYork}) et que je veux changer ce VO pour déménager à Adresse={10, Liberty Street, Washingtown}, alors je vais devoir assigner cette nouvelle adresse à Alice et à Bob? S'il était mutable, j'aurai juste eu à changer le VO de l'Adresse, et tous ceux habitant à cette adresse auraient automatiquement déménagés.

Ce qui me gène dans l'immutable, c'est qu'un VO partagé entre plusieurs entités ne peut pas être mis à jour (si immutable). Un DTO peut facilement l'être (car mutable).


RE: Valueobject - Max72 - 06-09-2015

C'est reparti ^^

Citation :J'éviterai, puisque cela mélange la "monnaie virtuelle" (UniformDevise) et la monnaie réelle Dollars.
En effet c'est à proscrire, c'était juste pour l'exemple.

Citation :D'ailleurs, comment tu convertis Dollars en Euros ?
Avec des classes toEuros(), toDollars() etc, si j'en ai le besoin, mais dans le cas actuel ça ne me semble pas indispensable.
Pour la suite, je ne comprends pas trop. Tu essayes de mélanger monnaies réelles et virtuelles, de mon coté je ne parle que de monnaies réelles dont on permet la comparaison (c'est bien ce que tu m'avais demandé ? ).
Si vraiment tu veux un code, je le ferai, mais j'en ai déjà assez fait non ? Smile Toi et moi savons que c'est réalisable, et je sais également qu'à force de vouloir me faire implémenter des méthodes ou des comportements dans mes VO, ça n'en sera plus, ça sera des objets tout court. Est-ce cela que tu voulais entendre ? Wink

Citation :Celle de VO ne colle pas avec ce que t'as dit au-dessus, puisque dans la définition, je peux mettre tous les comportements de la Terre que je veux: un Objet avec un tas de comportement, mais qui serait immutable et dont deux instances distinctes peuvent être égale serait donc un VO.
Oui un VO peut avoir un/des comportements. La définition se limite à : objet simple dont la comparaison non basée sur l'identité et immutable. Jamais dit qu'il ne pouvait pas posséder de comportement.
Pour l'égalité, c'est au dév de dire sur quoi se porte l'égalité : string en minuscule ou MAJ, entier, arrondi, ... Là en l’occurrence j'ai choisi de les comparer sur une devise uniforme, et ce à ta demande. C'est ça le problème ?

Citation :Un tableau associatif est mutable, c'est plutôt un DTO qu'un VO.
Jamais dit que c'était l'un ou l'autre. C'est un tableau, pas un objet Smile

Citation :Là, pour toi les VO sont la partie IE (Immutable, Equality) et les DTO la zone BE (Behaviorless, Equality). Donc un DTO mutable n'est pas un VO (mais un DTO Immutable est un sous-genre de VO)?
Un DTO mutable n'est pas un VO non, car le caractère mutable annule la définition d'un Value Object.
Un DTO immutable n'est pas un VO non plus, car il n'a pas à posséder de comportement. D'ailleurs sur ton graph on voit nettement la séparation avec No Behaviour.
Les VO, c'est uniquement ton IE, rien d'autre.

Citation :....
Ce qui me gène dans l'immutable, c'est qu'un VO partagé entre plusieurs entités ne peut pas facilement être édité. Un DTO peut facilement l'être (car mutable).
Mais leur boulot n'est pas le même.
Sinon pour le déménagement d'Alice et Bob, ça te prendra une déclaration par personne, à moins de créer une méthode qui permette de déménager plusieurs personnes (un tableaux de personnes en paramètre ?).
C'est pas la mort non plus Smile

[HS]
J'ai la sensation (ce n'est pas péjoratif, j'aime bien débattre sur des sujets que je maîtrise à peu près) que, comme sur le sujet East Compass, tu cherches absolument un exemple impossible ou me faire dire une grosse contradiction. C'est voulu ? Wink
[/HS]


RE: Valueobject - Xenos - 06-09-2015

Citation :je sais également qu'à force de vouloir me faire implémenter des méthodes ou des comportements dans mes VO, ça n'en sera plus, ça sera des objets tout court. Est-ce cela que tu voulais entendre ?
Citation : ou me faire dire une grosse contradiction. C'est voulu ?

Oui, parce que ta définition "Immutable + Egalité sur tout ou une partie des attributs" n'est pas cohérente, puisque de ce que tu dis, en ajoutant "trop" de comportements, ce n'est plus un VO. Mais s'il y a des comportements, ça peut quand même être un VO.
La définition n'est donc pas exacte. Si un objet qui a "trop" de comportements n'est plus un VO, mais qu'un VO peut avoir "quelques" comportements, alors ta définition doit être précisée (et je pense qu'elle sera alors très subjective, à cause du "trop").

De plus, si un VO = {Immutable + Egalité sur des attributs}, alors un DTO immutable={Immutable + Egalité sur les attributs + No Behavior} EST un VO particulier (mais tout VO n'est pas un DTO immutable, puisque le VO peut avoir des comportements, aka BIE est inclus dans IE).


Enfin, pour le cas de l'adresse, comment stockes-tu ça en BDD? Pour ma part, j'aurai une table de Personnes, où chacune est liée à une Adresse (table séparée), avec un lien N..1: N personnes peuvent avoir 1 même adresse. Si la BDD a la même structure que ton VO, chaque adresse de chaque personne devrait être répétée (lien 1..1) puisqu'il faut faire un "lookup" quand on veut déménager une adresse (pas une personne, une adresse).

Tiens, autre façon de présenter les choses: si Adresse={int numéro, Rue rue} et Rue={string Nom, Ville ville} et Ville={string ville, Pays pays} et Pays={string nom} (je pousse loin volontairement). Si un Pays change de nom, alors par immutabilité, il va falloir en créer un nouveau, puis chercher toutes les occurrences du vieux pays pour les remplacer par le nouveau. Mais comme ce vieux Pays apparait dans un "Ville" qui est un VO immutable, il va falloir créer de nouvelles Ville qui remplaceront les anciennes occurrences dans Rue... Mais Rue est un VO, il faut donc créer de nouvelles Rue... Et il faut créer de nouvelles Adresse... On recalcule la Terre entière alors qu'on a juste changé le nom d'un Pays !


RE: Valueobject - Max72 - 06-09-2015

Citation :Oui, parce que ta définition "Immutable + Egalité sur tout ou une partie des attributs" n'est pas cohérente, puisque de ce que tu dis, en ajoutant "trop" de comportements, ce n'est plus un VO. Mais s'il y a des comportements, ça peut quand même être un VO.
La définition n'est donc pas exacte. Si un objet qui a "trop" de comportements n'est plus un VO, mais qu'un VO peut avoir "quelques" comportements, alors ta définition doit être précisée (et je pense qu'elle sera alors très subjective, à cause du "trop").
Tu extrapoles beaucoup. Encore une fois, je n'ai jamais dit qu'il ne devait y avoir que quelques comportements ou qu'il ne devait pas en posséder 'de trop'. Les limites que je m'autorise sont celles de ne traiter QUE la valeur (attribut) qu'il contient, mais de pouvoir la retourner (sans la transformer) comme je le souhaite (int, string, devise, .........) et la comparer.

Citation :De plus, si un VO = {Immutable + Egalité sur des attributs}, alors un DTO immutable={Immutable + Egalité sur les attributs + No Behavior} EST un VO particulier (mais tout VO n'est pas un DTO immutable, puisque le VO peut avoir des comportements, aka BIE est inclus dans IE).
Si tu veux... J'ai déjà donné réponse plus haut à plusieurs reprises.

Oui, je stockerai ça en One to Many (je préfère, c'est plus clair Smile ). Dans ton blog, tu as bien plusieurs articles dans la même catégorie non ? Est-ce si dur que cela à gérer ? Je ne comprends pas. Si je veux faire déménager toutes ces personnes, il suffit d'un UPDATE sur ma BDD..

Pour le dernier paragraphe, j'ai la sensation que tu oublies que nous parlons d'un langage dynamique. Pourquoi aurais-je besoin d'écrire plusieurs fois partout dans mon code le nom d'un pays ? A la limite, une fois dans une classe ou un référential, mais après ? Et si tu parles de la BDD, idem, un UPDATE et c'est fini..

Edit : Pour aller plus vite, tu peux clairement nous dire comment toi tu vois les choses et les solutions que tu mettrais en place, non ?


RE: Valueobject - Xenos - 06-09-2015

Citation : Les limites que je m'autorise sont celles de ne traiter QUE la valeur (attribut) qu'il contient, mais de pouvoir la retourner (sans la transformer) comme je le souhaite (int, string, devise, .........) et la comparer

Du coup, ajoute cela à ta définition.

Chaque Personnage dans le code possède une Adresse, liée à une Rue, liée à une Ville, liée à un Pays. Plusieurs personnages peuvent habiter au même endroit, plusieurs Adresses peuvent être liées à la même Rue, plusieurs Rues peuvent être liées à la même Ville et plusieurs Villes se trouvent dans le même Pays. L'ONU possède la liste des pays (ils s'en foutent de la liste des gens). L'ONU déclare que le Pays "Gaule" change de nom pour "France".

Puisque tu le demandes, pour ma part, si je devais extraire les classes de la BDD pour faire ces traitements, alors chacune des classes évoquées serait mutable et plusieurs personnages partageraient donc la même instance de "Adresse" (s'ils ont la même Adresse), plusieurs adresses partageraient la même instance de "Rue", etc. Je change simplement le pays en changeant le nom dans l'instance "Pays" correspondant à la France (c'est toujours le même Pays).

Avec des (VO) immutables, tu fais ça comment?


RE: Valueobject - Max72 - 06-09-2015

Et bien je change le nom "Gaulle" par "France" dans mon code, et comme toutes mes infos sont tirées de ma BDD, j'imagine que je n'ai qu'une ou 2 utilisations du mot "Gaulle" dans mon code, ce qui me prend (en comptant le temps de la recherche) environ 8 secondes et demi avec mon IDE.

Idem pour la BDD, ou j'exécute :
Code :
UPDATE Personnages
SET Pays = 'France'
WHERE Pays = 'Gaulle'
Là encore, le temps estimé pour modifier la planète entière est estimé à moins de 20 secondes Smile

Sérieusement, je ne vois pas où est le soucis.


RE: Valueobject - Xenos - 06-09-2015

Citation :dans mon code
J'ai dû mal m'exprimer: "Gaule" n'est pas une constante de classe, c'est une valeur issue du Runtime.
Si tu sors les données de la BDD pour instancier tes VO, tu peux alors te retrouver avec plusieurs instances de VO représentant la "Gaule". Comment, maintenant, au Runtime, l'ONU fait-il pour changer le nom "Gaule" en "France"? Si tu édites la BDD, tous les objets qui en ont été extrait avant ne seront pas impactés (et s'appelleront toujours "Gaule"; si tu les sauves en BDD tu perds la modification "France"). Comment l'ONU fait-il pour dire au reste du code que "Gaule" vient de changer de nom?

Les deux seules solutions que je vois sont: l'ONU a une référence vers l'unique objet représentant la Gaule et les autres objets référencent cet unique objet, qui doit donc être mutable pour autoriser un changement de nom; ou bien l'altération des données du modèle doit se faire avant l'extraction des données par le code PHP (donc toute cette logique métier se trouve directement dans le SQL).