Seguendo la O dei S.O.L.I.D. Principles, il codice dovrebbe essere chiuso alla modifica ma aperto
all'evoluzione. Ma cosa vuole dire nel concreto? La questione può
essere semplificata se ragioniamo per comportamenti.
Il nostro
product owner chiede di mostrare a video tutti gli alunni con una età
maggiore di 9 anni.
Scriviamo il test che verifica l'oggetto FilterStudentsByAge:
[SetUp]
public
void
SetUp()
{
sut
= new
FilterStudentsByAge();
}
[Test]
public
void
Test()
{
sut.Filter(10.To<Age>(),
new[]
{ new
Student
{ Age = 10.To<Age>()}});
// completiamo il test...
// completiamo il test...
}
Potremmo far restituire la lista filtrata; é una strada,
sconsiglio quando é possibile di usare valori di ritorno.
Se seguiamo questo concetto l'unico modo per testare l'oggetto é
verificare la sua interazione con un altro oggetto.
Modifichiamo il test per aggiungere la dipendenza ad un'altra
interfaccia.
[SetUp]
public
void
SetUp()
{
useStudent
= new
Mock<IUseStudent>();
sut
= new
FilterStudentsByAge(useStudent.Object);
}
[Test]
public
void
Test()
{
var
student = new
Student
{Age = 10.To<Age>()};
sut.Filter(10.To<Age>(),
new[]
{ student });
useStudent.Verify(m=>m.Do(student));
}
Ora il test ci permette di capire se Filter ha il comportamento
che desideriamo e interagisce con l' interfaccia IUseStudents.
Implementiamo Filter nell' oggetto FilterStudentsByAge.
public
class
FilterStudentsByAge
{
private
readonly
IUseStudent
useStudent;
public
FilterStudentsByAge(IUseStudent
useStudent)
{
this.useStudent
= useStudent;
}
public
void
Filter(Age
age, IEnumerable<Student>
students)
{
foreach
(var
student in
students)
{
if
(student.Age < age)
continue;
useStudent.Do(student);
}
}
}
Il test passa ma il codice non può andare in produzione, non
esiste nessun oggetto che implementa IUseStudents. Ora creiamo l'oggetto che mostra gli studenti a video come richiesto dal product
owner.
public
class
DisplayStudentAtVideo
: IUseStudent
{
public
void
Do(Student
student)
{
if
(student == null)
return;
Console.WriteLine("Studente
{0}
{1}",
student.Lastname,
student.Name);
}
}
Ora il product owner ci chiede di scrivere su un file gli studenti
oltre a mostrarli a video. La prima reazione è di modificare l' oggetto DisplayStudentAtVideo e aggiungere il codice per scrivere il
file.
Respiriamo e proviamo a seguire la strada che vi consiglio.
Scriviamo un oggetto per aggiungere il nuovo comportamento.
public
class
WriteStudentsIntoFile
: IUseStudent
{
private
readonly
TextWriter
stream;
public
DisplayStudentAtVideo(string
filename)
{
stream
= new
FileInfo(filename).AppendText();
}
public
void
Do(Student
student)
{
if
(student == null)
return;
stream.WriteLine("Studente
{0}
{1}
[età: {2}]",
student.Lastname,
student.Name,
student.Age);
stream.Flush();
}
}
Bene, ma ora come facciamo ad usarle insieme?
Creiamo un' altro oggetto che ha il ruolo di gestire l'utilizzo di
un elendo di oggetti che rispettano il contratto IUseStudent.
public
class
UseStudentsComposite
: IUseStudent
{
private
readonly
IUseStudent[]
inners;
public
UseStudentsComposite(IUseStudent[]
inners)
{
this.inners
= inners;
}
public
void
Do(Student
student)
{
foreach
(var
item in
inners)
item.Do(student);
}
}
A questo punto il test continua a passare perche non abbiamo
modificato il comportamento di FilterStudentsByAge, ma dobbiamo
aggiungere i test per i nuovi oggetti. Vediamo il codice di
produzione per la prima richiesta:
new
FilterStudentsByAge(
new
DisplayStudentAtVideo())
.Filter(10.To<Age>(),
new[]
{
new
Student(),
new
Student(),
new
Student()
});
new
FilterStudentsByAge(
new
UseStudentsComposite(
new
IUseStudent[]
{
new
DisplayStudentAtVideo(),
new
WriteStudentsIntoFile(
@"c:\temp\dumpStudents.txt")
}))
.Filter(10.To<Age>(),
new[]
{
new
Student(),
new
Student(),
new
Student()
});
Si può notare che una successiva richiesta di aggiungere un nuovo
comportamento per condividere le informazioni degli studenti, si
tramuterebbe in un nuovo oggetto da istanziare nell'array gestito da
UseStudentsComposite.
Concludendo:
- aggiungete oggetti per definire nuovi comportamenti e separare le responsabilità;
- cercate di modificare il codice solo per iniettare i nuovi comportamenti;
- fate refactoring per rispettare questi principi.
Nessun commento:
Posta un commento