Valueobject
#1
La question s'étant posée sur le débat East (qui vire à un fourre-tout, et je reconnais y être pour 50%), j'aimerai savoir comment vous définissez les valueobject.

Pour ma part, je pensais initialement qu'un ValueObject était un conteneur de donné dont le seul rôle est de... contenir ces données. Il doit également permettre d'y accéder, et s'il est mutable, de les modifier. Un valueobject immutable (dont les données ne sont pas modifiables) me semble plus approprié mais pas forcément obligatoire.
Donc finalement, c'est un objet avec seulement un constructeur, des getters (soit explicites en PHP, soit "implicite" en Java, en accédant aux attributs public final par exemple), et des setters si besoin (mutable).


Après relecture de la définition: "In computer science, a value object is a small object that represents a simple entity whose equality is not based on identity: i.e. two value objects are equal when they have the same value, not necessarily being the same object.[...]two value objects created equal, should remain equal." (Wikipedia), je me pose quelques questions:

• Tous les valueobjects doivent-ils être obligatoirement immutables, ou certains peuvent-ils l'être s'ils en ont une raison?

• En PHP, on implémenter les getter (et setter, suivant la réponse précédente), mais doit-on implémenter dans le valueobject une méthode testant l'égalité entre deux valueobject?

• Peut-on, dans la suite logique, implémenter des opérateurs de comparaison dans le valueobject ?

• Teste-t-on un valueobject ? S'il n'y a que les getter/setter, ok, c'est useless comme test, mais si on accepte les comparaisons?

• Un valueobject doit-il ne contenir qu'une et une seule donnée (par exemple, la quantité de monnaie) ou peut-il en contenir plusieurs (la valeur faciale d'un billet + sa date d'impression + son numéro de série)


Ces questions se posent dans le cadre des "bonnes pratiques", de sorte que si je parle de valueobject au boulot à un collègue, on ait la même définition.
Répondre
#2
les nombres imaginaires
les vecteurs

ce sont des valueObjects
ils ont plusieurs données

pour moi la notion de valueObjects revient à la notion de type utlisateur (ie autre chose que int, float, string fournis par défaut...) mais avec les mêmes comportements comparaison, agrégation, etc...

ni plus ni moins
[WIP]projet Rivages
[WIP]projet Arthur (comme si ça suffisait pas d'un...)
Répondre
#3
J'aime assez cette définition. Mais du coup, est-ce censé de tester ses valueobject quand ils sont complexes? Par exemple, un vecteur ?

Si un valueobject contient un nombre imaginaire, les calculs sur les nombres imaginaires se feraient où? Par exemple, l'addition, la multiplication, la norme, l'argument, la conversion en exponentielle,... ça irait dans le valueobject, ou dans un objet séparé, dédié aux calculs sur des nombres imaginaires (donc, une classe séparée qui traite des valueobject NombreImaginaire)? Perso, je ferai une classe séparée.


Pour répondre à srm:
srm a écrit :Tu test un int ? Un int ou tu peux faire int > int à un comportement ?
J'aime bien l'idée de voir le valueobject comme un typage de donnée. Si le typage est complexe, il est censé de le tester, donc tester les typades de données me semble possible. Ce n'est pas réalisable en revanche pour int car il s'agit d'un typage du langage, un genre de ValueObject définit dans le langage et non dans le code. Et il ne fait clairement pas sens de tester un composant du langage (int ou l'opérateur >). En revanche, il est censé de tester ses valueobjects.
Répondre
#4
Je pense qu'un ValueObject n'a de sens que dans un langage à typage faible pour imiter un typage fort, et dans tout langage n'ayant pas de types composés pour imiter les types composés.

Citation :Si un valueobject contient un nombre imaginaire, les calculs sur les nombres imaginaires se feraient où? Par exemple, l'addition, la multiplication, la norme, l'argument, la conversion en exponentielle,... ça irait dans le valueobject, ou dans un objet séparé, dédié aux calculs sur des nombres imaginaires (donc, une classe séparée qui traite des valueobject NombreImaginaire)? Perso, je ferai une classe séparée.

Perso, je l'implémenterai dans la class même.

Si ton langage est "tout objet", par exemple Scala, pour un integer l'opérateur + est une méthode de la classe integer (je ne connais pas le vrai nom de la classe, c'est un exemple ; c'est probablement number car tu peux ajouter des integer et des float). Tu peux faire 4.+(5), ce qui renvoie 9.

En PHP, on n'a pas ce genre de méthodes pour un simple integer, et on n'a pas de controle des types en entrée et sortie de fonction. Donc ton ValueObject sert typiquement à implémenter ça dans le cadre de Gold qui est un simple container de Gold.

Pour un nombre imaginaire, tu entres dans un type de données composées (partie réelle et partie imaginaire représentant deux informations contenues dans une seule entité, donc une entité composée). Inutile donc d'avoir deux classes, l'une pour le type et l'autre pour les algorithmes de calculs. Ici il ne s'agit pas de POO mais simplement d'implémentation : on a tendance à grouper la représentation d'une donnée (les propriétés genre private $imaginaire) aux méthodes les utilisant.

En Erlang ou Haskell tu auras une déclaration de type et un module permettant des les manipuler.

Par exemple, voici un module vecteurs pour Erlang :

[code=Erlang]-module(vecteurs).

-export([new/2,new/3,add/2]).

-type vecteur_2D() :: {vecteur_2D, X :: float(), Y :: float()}.
-type vecteur_3D() :: {vecteur_3D, X :: float(), Y :: float(), Z :: float()}.


-spec new(float(), float()) -> vecteur_2D().
-spec new(float(), float(), float()) -> vecteur_3D().
-spec add(Vec,Vec) -> Vec
when Vec :: vecteur_2D() | vecteur_3D().

new(X,Y) -> {vecteur_2D, X, Y}.
new(X,Y,Z) -> {vecteur_3D, X, Y, Z}.

add({vecteur_2D, Xa, Ya},{vecteur_2D, Xb, Yb}) -> {vecteur_2D, Xa+Xb, Ya+Yb};
add({vecteur_3D, Xa, Ya, Za},{vecteur_3D, Xb, Yb, Zb}) -> {vecteur_3D, Xa+Xb, Ya+Yb, Za+Zb};

add({vecteur_3D, Xa, Ya, Za}, {vecteur_2D, Xb, Yb}) -> {vecteur_3D, Xa+Xb, Ya+Yb, Za};
add({vecteur_2D, _, _} = A, {vecteur_3D, _, _, _} = B) -> add (B,A).
[/code]

Et voici comment on peut s'en servir en dehors du module :

[code=Erlang]V = vecteurs.
%% vecteurs
V1 = V:new(1,2).
%% {vecteur_2D,1,2}
V2 = V:new(10,20,30).
%% {vecteur_3D,10,20,30}
Vadd = V:add(V1,V2).
%% {vecteur_3D,11,22,30}[/code]

On peut faire pareil avec des classes/propriétés/méthodes car ce sont les outils que proposent PHP ou d'autres langages objets n'ayant pas de types. Pour résumer mon opinion, les ValueObject sont l'équivalent de cela dans les langages proposant des classes et méthodes pour définir des types, mais on peut utiliser ces ValueObjects en programmation procédurale, objet ou fonctionnelle.

Quant aux tests, je crois qu'il est tout à fait censé de tester ce module via de simples tests unitaires sur les fonctions qui manipulent le type. Comme le type vecteur_2D n'est pas exporté (l'autre non plus d'ailleurs), il n'est défini qu'au sein du module, le code extérieur manipule uniquement des variables (V1, V2 et Vadd). On ajoutera donc nos tests dans le module, en bas du fichier, entre des directives de préprocesseur qui n'ajouteront le code de test que lors d'une compilation en mode test afin de garder du code léger en prod.

Ou bien on exportera les types afin de pouvoir écrire -spec setVaisseauDestination(vecteurs::vecteur_3D(), vaisseau()) -> ok | {error, Reason :: any() }. dans un autre module, et écrire nos tests dans un fichier à part.
Répondre
#5
Mais si les méthodes sont dans le valueobject, comment cela se passe si un valueobject peut être associé à plusieurs algères? Par exemple, un valueobject décrivant un billet de banque avec sa valeur faciale, son numéro de série et sa date, peut être associé à plusieurs algèbres: on peut comparer les billets selon leur valeur faciale (assez naturel), leur numéro de série ou leur date d'impression.

Perso, j'aurai mis les 3 données dans un valueobject, créer les classes de calcul à part (1 part algèbre), dont j'aurai éventuellement décoré le valueobject.
Répondre
#6
un valueobject est une valeur (composé d'un ou plusieurs éléments)

un numéro d'inventaire n'est pas un élément de valeur mais un élément de fonction. Si on stocke le numéro d'inventaire c'est pour utiliser les notions d'inventaire

dans ce cadre 1 billet + 1 billet = 1 billet et 1 billet

un billet (avec cet aspect numéro d'inventaire ou cet aspect date) ne peut pas être un valueobject

sinon tout serait value object, un user (date de naissance, numero de sécu) est il un value object ? non


et comme niahoo toute méthode de calcul (et de conversion ie float => int, int => float) se fait dans l'objet valueobject
[WIP]projet Rivages
[WIP]projet Arthur (comme si ça suffisait pas d'un...)
Répondre
#7
Un valueObject à aussi de l'utilité dans un langage fortement typé ou qui a des types composés.
D'ailleurs à l'époque ça a été créé quand il existait que ce type de langage 16

[code=scala]
case class Gold(amount: Int)
{
require(amount >= 0)
}
[/code]

Car comme je le montre avec cet exemple, un valueObject peut valider son domaine fonctionnel.
Répondre
#8
De quelle époque tu parles ? Avec un systeme de types correct tu peux faire cette validation à la compilation sans nécessité de cette classe.
Répondre
#9
Bah quand le ValueObject pattern a été créé.
Tu peux donner un exemple ?
Répondre
#10
merci je ne suis pas stupide, j'ai bien compris le sens de la phrase "D'ailleurs à l'époque ça a été créé quand il existait que ce type de langage" ; inutile de paraphraser, peux tu développer ?

Pour l'exemple regarde une déclaration de constructeur de type en haskell, désolé, la flemme d'écrire du code sur mon mobile.
Répondre




Utilisateur(s) parcourant ce sujet : 1 visiteur(s)