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.
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
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
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
En Erlang ou Haskell tu auras une déclaration de type et un module permettant des les manipuler.
Par exemple, voici un module
Et voici comment on peut s'en servir en dehors du module :
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
Ou bien on exportera les types afin de pouvoir écrire
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 :-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).
Et voici comment on peut s'en servir en dehors du module :
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}
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.