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 - niahoo - 02-09-2015

Bah faut pas pousser, faut penser au cas d'utilisation quand même. Qui va comparer deux adresses juste sur le nom de la rue ?

tenez, une implémentation traduite en Elixir. ATTENTION, ce code est de mauvaise qualité, il représente juste un équivalent de la version PHP mais il est beaucoup trop compliqué pour rien. On préfèrera utiliser des variables scalaires au sein de la classe Adresse

(je mets la coloration en ruby pour que ça soit plus joli)

defmodule Adresse do

defmodule Numero do
defstruct numero: nil

def is_equal?(%Numero{numero: x}, %Numero{numero: x}), do: true
def is_equal?(_, _), do: false
end

defmodule Rue do
defstruct rue: nil

def is_equal?(%Rue{rue: r1}, %Rue{rue: r2}) do
String.downcase(r1) === String.downcase(r2)
end
def is_equal?(_, _), do: false
end

defmodule Ville do
defstruct ville: nil

def is_equal?(%Ville{ville: v1}, %Ville{ville: v2}) do
String.downcase(v1) === String.downcase(v2)
end
def is_equal?(_, _), do: false
end


defstruct numero: nil, rue: nil, ville: nil

def new(n, r, v) do
%Adresse{numero: %Numero{numero: n}, rue: %Rue{rue: r}, ville: %Ville{ville: v}}
end

def is_equal?(%Adresse{numero: n1, rue: r1, ville: v1}, %Adresse{numero: n2, rue: r2, ville: v2}) do
Numero.is_equal?(n1, n2) and
Rue.is_equal?(r1, r2) and
Ville.is_equal?(v1, v2)
end
def is_equal?(_, _), do: false

end

defimpl String.Chars, for: Adresse do
def to_string(a), do: "n° #{a.numero} rue #{a.rue} à #{a.ville}"
end
defimpl String.Chars, for: Adresse.Numero do
def to_string(x), do: Kernel.to_string x.numero
end
defimpl String.Chars, for: Adresse.Rue do
def to_string(x), do: x.rue
end
defimpl String.Chars, for: Adresse.Ville do
def to_string(x), do: x.ville
end


adresse_de_jean = Adresse.new(4, "de la mairie", "Paris")
adresse_de_bob = Adresse.new(44,"de Napoléon","Paris")
adresse_de_jeanne = Adresse.new(4, "de la mairie", "PARIS")

IO.puts "Jean habite au #{adresse_de_jean}"
IO.puts adresse_de_jean === adresse_de_bob
IO.puts adresse_de_jean === adresse_de_jeanne
IO.puts Adresse.is_equal?(adresse_de_jean, adresse_de_jeanne)


Jean habite au n° 4 rue de la mairie à Paris
false
false
true



RE: Valueobject - niahoo - 02-09-2015

Et voilà une version qui me satisfait beaucoup plus. J'en ai profité pour enlever le franglais (Adresse.is_equal ...)

defmodule Adresse do

defstruct numero: nil, rue: nil, ville: nil

def new(n, r, v), do: %Adresse{numero: n, rue: r, ville: v}

def identique?(%Adresse{numero: x, rue: r1, ville: v1}, %Adresse{numero: x, rue: r2, ville: v2}) do
meme_rue?(r1,r2) and meme_ville?(v1,v2)
end
def identique?(_, _), do: false

def meme_rue?(a, b), do: lccomp(a, b)
def meme_ville?(a, b), do: lccomp(a, b)

defp lccomp(a, b), do: String.downcase(a) === String.downcase(b)

end

defimpl String.Chars, for: Adresse do
def to_string(a), do: "n° #{a.numero} rue #{a.rue} à #{a.ville}"
end

adresse_de_jean = Adresse.new(4, "de la mairie", "Paris")
adresse_de_bob = Adresse.new(44,"de Napoléon","Paris")
adresse_de_jeanne = Adresse.new(4, "de la mairie", "PARIS")

IO.puts "Jean habite au #{adresse_de_jean}"
IO.puts adresse_de_jean === adresse_de_bob
IO.puts adresse_de_jean === adresse_de_jeanne
IO.puts Adresse.identique?(adresse_de_jean, adresse_de_jeanne)

Code :
Jean habite au n° 4 rue de la mairie à Paris
false
false
true



RE: Valueobject - Xenos - 02-09-2015

Citation :Bah faut pas pousser, faut penser au cas d'utilisation quand même. Qui va comparer deux adresses juste sur le nom de la rue ?
Donc, il ne faut pas de méthode "isEqual()" pour la classe "Rue" si on considère que cela ne fait pas sens de comparer deux rues. Je Trouve que c'est c'est là le drame de beaucoup de projets "objets": on crée des classes avec des méthodes qui n'ont de sens que dans un contexte d'utilisation particulier.
Soit la méthode "Rue::isEqual()" existe, est fiable et ne compare que deux rues sans se soucier de "pourquoi est-ce que cette méthode est appelée / dans quelle cas est-elle appelée" (donc, se baser sur le seul nom de rue est léger, ou alors il faut que la classe représente un NomDeRue et non une Rue, puisqu'une Rue n'est pas représentée par son seul nom), soit la méthode n'existe pas (voire, la classe n'existe pas si elle est "interne" à la classe Adresse).

Du coup, si dans l'exemple Elixir, meme_rue() est privée, c'est envisageable (puisque le scope est limité à la classe, donc on sait dans quel contexte ces méthodes privées son appelées). Si c'est publique, c'est problématique:


adresse_de_jean = Adresse.new(4, "de la mairie", "Paris")
adresse_de_bob = Adresse.new(44,"de la mairie","Lyon")

Jean et Bob habitent la même rue pour vous? C'est une question qui fait sens (je peux avoir besoin de savoir s'ils habitent la même rue sans être forcément à la même adresse) mais la réponse donnée par meme_rue() serait "true" il me semble...

Tiens, typiquement, c'est exactement comme si on comparait deux personnes dans une BDD uniquement sur la base de leur nom: les homonymes, ça existe, c'est même pour cela qu'on utilise un id dédié.


RE: Valueobject - niahoo - 02-09-2015

Les rues n'ont pas d'ID en France, mais c'est vrai, si tu veux savoir si c'est vraiment la même rue, il faudrait prendre l’adresse en paramètre :

Code :
  def meme_rue?(%Adresse{rue: r1, ville: v1}, %Adresse{rue: r2, ville: v2}) do
    meme_rue?(r1,r2) and meme_ville?(v1,v2)
  end

Ensuite, au niveau métier, il n'y a qu'un cas ou on veut comparer deux adresses : savoir si c'est la même. Que bob et jean habitent dans la même rue ou dans la même ville c'est pareil. Et si tu bosses sur du réseau (assainissement, eau potable, électricité, éclairage, ...) dans une rue, les recherches se font par parcours de graphe ou par secteurs.

Quand tu vois la gueule des données des communes ou conseils généraux de toute façon, on est loin de telles préoccupations).

Mais du coup dans l'absolu en PHP, Adresse devrait juste avoir numéro, (alt: bis, ter, ...), type de rue, code postal et Rue.
Rue devrait avoir Ville.
Ville devrait avoir Département
etc.

Bon, ensuite c'est un exemple son gist, ça sert à rien de vouloir en faire un AdressFactoryFactoryFactory. Il est clair que dans ce cas d'utilisation c'est Nom de Rue dont il est question. D'ailleurs, une rue ne sert que pour les adresses. Le reste est géré par d'autres entités.


RE: Valueobject - srm - 02-09-2015

Dans ton exemple niahoo le numéro de la rue etc, ne sont pas des valueobject comme chez lui


RE: Valueobject - niahoo - 02-09-2015

Dans le second exemple, en effet. J'ai expliqué pourquoi. Ce sont des objets immutables donc c'est inutile.


RE: Valueobject - Xenos - 02-09-2015

Citation :Mais du coup dans l'absolu en PHP, Adresse devrait juste avoir numéro, (alt: bis, ter, ...), type de rue, code postal et Rue.
Rue devrait avoir Ville.
Ville devrait avoir Département
etc.

Ouep. Ca me rappelle les formes normales tout ça Smile


RE: Valueobject - srm - 02-09-2015

(02-09-2015, 03:45 PM)niahoo a écrit : Dans le second exemple, en effet. J'ai expliqué pourquoi. Ce sont des objets immutables donc c'est inutile.

C'est pas inutile, puisque dans son exemple il vérifie que la rue par exemple à un format adapté, c'est bien au value object rue de vérifier ça.


RE: Valueobject - niahoo - 02-09-2015

Bof. C'est une différence de point de vue. Pour moi, l'objet adresse peut très bien checker si la rue est une chaine. Il est vrai que j'ai complètement oublié de vérifier le typage de ce qu'on reçoit. Mais on s'en fout, de toute façon les ValueObjects sont inutiles dans un langage immutable. C'était pour montrer la syntaxe un peu.


RE: Valueobject - Xenos - 02-09-2015

Je rejoins niahoo: Adresse peut parfaitement checker ses différents data fields puisque "Rue, Ville" sont ici du ressort de la logique interne de l'Adresse.

Si on isole "Rue" dans une classe à part, on doit en gérer correctement le "equals" puisque cela devient un Bean indépendant. Si on ne le sort pas, on n'a pas à le gérer, et on peut faire un Adresse.memeRue(Adresse, Adresse) qui check juste les valeurs de leur rue, ou (plus logique) qui check rue+ville.