Come
illustrato in altri post, sostengo che l'utilizzo dei tipi "primitivi" per identificare dei concetti, può portare a problemi di design e
manutenzione, oltre ad insidiosi bug.
Prendiamo
ad esempio l'oggetto persona così definito:
public
class
Person
{
public
string
Name { get; set; }
public
string
Lastname { get; set; }
public
double
Height { get; set; }
public
double
Weight { get;
set;
}
public
int
Age { get; set; }
}
Vediamo un primo possibile problema, definiamo una Factory per
istanziare un oggetto Person:
public static Person Create(string name,
string
lastname,
double
height,
double
weight,
int
age)
{
return
new
Person
{
Name
= name,
Weight
= height,
Lastname
= lastname,
Height
= height,
Age
= age
};
}
Non
notate nulla di strano? Se cerchiamo di compilare, non abbiamo nessun problema. Solo se scriviamo un test oppure eseguiamo il
codice, ci potremmo accorgere di un problema. Allora non ci resta che scrivere un test.
[Test]
public
void
CreatePersonTest()
{
var
person = Person.Create("Claudio",
"Pattarello",
179, 95, 36);
Assert.AreEqual("Claudio",
person.Name);
Assert.AreEqual("Pattarello",
person.Lastname);
Assert.AreEqual(179,
person.Height);
Assert.AreEqual(95,
person.Weight);
Assert.AreEqual(36,
person.Age);
}
Se
eseguiamo il test otteniamo questo risultato:
Test
'CreatePersonTest' failed:
Expected:
95
But
was: 179.0d
Questo
accade perché abbiamo usato lo stesso tipo per indicare due concetti
diversi, se le proprietà Height e Weight fossero dei tipi che
rappresentano coerentemente i valori che contengono, ci saremmo
accorti di un problema direttamente a compile-time.
Vediamo
cosa succede se definiamo i tipi Height e Weight e li sostituiamo ai
double.
public
class Height
{
public
double
Value { get;
set;
}
}
public
class Weight
{
public
double
Value { get;
set;
}
}
ora
modifichiamo la classe e la factory:
public
class
Person
{
public
string
Name{get;set;}
public
string
Lastname{get;set;}
public
Height
Height{get;set;}
public
Weight
Weight { get;
set;
}
public
int
Age{get;set;}
}
public
static
Person
Create(string
name,
string
lastname,
Height
height,
Weight
weight,
int
age)
{
return
new
Person
{
Name
= name,
Weight
= height,
Lastname
= lastname,
Height
= height,
Age
= age
};
}
Se
compiliamo otteniamo ben tre errori:
- Argument 3: cannot convert from 'double' to 'Height'
- Argument 4: cannot convert from 'double' to 'Weight'
- Cannot implicitly convert type 'Height' to 'Weight'
I
primi due errori indicano l'impossibilità nel test di convertire 179
in Height e 95 in Weight; l'ultimo errore ci avvisa del problema che
ci saremmo accorti solo in fase di test, ovvero che abbiamo assegnato
alla property Weight il contenuto del parametro height.
Come
potete notare, se ci sforziamo di definire i tipi necessari e non di utilizzare solo quelli disponibili, possiamo prevenire molti
problemi.
Ora
vediamo come poter creare i nostri tipi “primitivi”, definiamo un
oggetto generic che utilizzeremo per creare i nostri tipi:
public
class ValueObject<T>
{
public
T Value { get;
set;
}
public
static
implicit
operator
T(ValueObject<T>
obj)
{
return
obj.Value;
}
}
Ora,
Height e Weight possono essere implementati facilmente:
public
class Weight
: ValueObject<double>
{}
public
class
Height
: ValueObject<double>
{}
Ora
per utilizzare la factory dovremmo scrivere:
var
person = Person.Create("Claudio",
"Pattarello",
new
Height { Value = 179 },
new
Weight { Value = 95 },
36);
Trovo questo codice troppo verboso, ed è per questo che preferisco, in questo
caso, usare un extension method:
public
static class
TypeExtensions
{
public
static
T To<T>(this
double
value)
where
T : ValueObject<double>,
new()
{
return
new
T { Value = value };
}
}
ora
la sintassi per utilizzare la factory diventa:
var
person = Person.Create("Claudio",
"Pattarello",
179d.To<Height>(),
95d.To<Weight>(),
36);
Potete
per tutti gli altri tipi primitivi creare l'extension method per
avere una sintassi più compatta.
Cercate di non essere pigri e definite tutti i tipi che vi servono, nel lungo termine otterrete solo benefici da questa pratica.
Cercate di non essere pigri e definite tutti i tipi che vi servono, nel lungo termine otterrete solo benefici da questa pratica.