Vi
ricordate la richiesta iniziale: "esportare la lista degli
studenti in tre formati diversi e gli elementi devono essere separati
ogni 10 elementi".
Ora
quel simpaticone del product owner ci chiede: "vorrei che la
lista a video non sia separata, invece la lista esportata in xml sia
paginata a 10 elementi e la lista esportata su csv deve avere una
paginazione di 20 elementi".
Inizialmente
usando l'ereditarietà avevamo scritto questa classe base:
public
abstract
class
ExportStudentsWithSeparatorEachTenLines
{
public
void
Save(Student[]
students)
{
var
linesCounter = 0;
if
(students != null)
foreach
(var
student in
students)
{
if
(++linesCounter%10 == 0)
WriteSeparator();
Export(student);
}
}
protected
abstract
void
Export(Student
student);
protected
abstract
void
WriteSeparator();
}
E
avevamo creato tre classi che si dovevano occupare di esportare i
dati nei tre formati richiesti.
Ora
abbiamo un comportamento che rimane comune: l'iterazione dell' array
di studenti.
Purtroppo
la logica per inserire il separatore é cambiata.
Come
procediamo?
Rinominiamo
la classe base in ExportStudendsWithSeparator.
Facciamo
una prova, inseriamo nel costruttore della classe base un parametro
che indica il numero di paginazione per le classi derivate:
protected
ExportStudentsWithSeparator(int
pageSize)
{
this.pageSize
= pageSize;
}
Modifichiamo
il codice per capire come dobbiamo paginare:
Vediamo
il codice modificato delle sotto classi:
public
class
ExportStudentsToXml
: ExportStudentsWithSeparator
{
public
ExportStudentsToXml() : base(10)
{ }
//
aggiungere il codice per implementare il formato
//
[...]
}
public
class
ExportStudentsToCsv
: ExportStudentsWithSeparator
{
public
ExportStudentsToCsv() : base(20)
{ }
//
aggiungere il codice per implementare il formato
//
[...]
}
Bene
bene, sembra funzionare, ma ho un dubbio:
public
class
ExportStudentsToVideo
: ExportStudentsWithSeparator
{
public
ExportStudentsToVideo() : base(/*
e qui cosa ci metto?*/)
{ }
//
aggiungere il codice per implementare il formato
//
[...]
}
Per uniformità dovremmo inserire zero, ma avremmo un risultato
inaspettato con il codice esistente, una bella Division by zero.
Ok
sono in crisi, modifico la classe base, inserisco un bel IF,
se il parametro ha valore zero allora salto il comportamento di
inserire il separatore.
if
(pageSize
!=0
&& ++linesCounter % pageSize == 0)
Questa
soluzione non mi piace, proviamo un'altra strada e creiamo un nuovo
template metodo che hai il compito di indicare quando è il momento
di inserire un nuovo separatore.
public
abstract
class
ExportStudentsWithSeparator
{
private
readonly
int
pageSize;
protected
ExportStudentsWithSeparator()
{
this.pageSize
= pageSize;
}
public
void
Save(Student[]
students)
{
var
linesCounter = 0;
if
(students != null)
foreach
(var
student in
students)
{
if
(NewSeparator(++linesCounter))
{
WriteSeparator();
linesCounter
= 0;
}
Export(student);
}
}
protected
abstract
void
Export(Student
student);
protected
abstract
void
WriteSeparator();
protected
abstract
NewSeparator();
}
Ora
vediamo come modificare le classi derivate:
public
class
ExportStudentsToXml
: ExportStudentsWithSeparator
{
protected
override
bool
NewSeparator(int
lineCount)
{
return
lineCount%10 == 0;
}
//
aggiungere il codice per implementare il formato
//[...]
}
La
classe che stampa a video ha una forma più elenegate di prima:
public
class
ExportStudentsToVideo
: ExportStudentsWithSeparator
{
protected
override
bool
NewSeparator(int
lineCount)
{
return
false;
}
//
aggiungere il codice per implementare il formato
//[...]
}
Con
questa scelta abbiamo evitato di inserire un IF, ma abbiamo
comunque duplicato del codice quando modifichiamo la classe che
esporta in CSV:
public
class
ExportStudentsToCsv
: ExportStudentsWithSeparator
{
protected
override
bool
NewSeparator(int
lineCount)
{
return
lineCount%20 == 0;
}
//
aggiungere il codice per implementare il formato
//[...]
}
Inoltre,
notate che abbiamo fatto molte modifiche, infatti per esaudire la
richiesta sono state modificate ben 4 classi e abbiamo pensato ha due
soluzioni prima di trovare la strada “giusta”.
Non
so voi ma ho una brutta sensazione e se domani arriva il product
owner con un'altra brillante idea?
Ok,
ora vi propongo una strada diversa, vi ricordate questo codice?
new
ExporterStudentsList(
new
ExportStudentWithSeparator(
new
ExportStudentToVideo(),
new
SeparatorToVideo()))
.Export(arrayOfStudents);
Proviamo
a risolvere il problema di esportare la lista degli studenti senza
separatore. Vediamo se è possibile con la composizione:
new
ExporterStudentsList(new
ExportStudentToVideo())
.Export(arrayOfStudents);
Cavoli,
ho finito!!!
Per
le altre due richieste, possiamo procedere come prima, aggiungendo il
parametro per definire la dimensione delle pagine nel
costruttore di ExportStudentWithSeparator.
public
class
ExportStudentWithSeparator
: IExportStudent
{
private
int
linesCounter;
private
IExportStudent
inner;
private
ISeparator
separator;
private
readonly
int
pageSize;
private
byte
counter;
public
ExportStudentWithSeparator(IExportStudent
inner,
ISeparator
separator,
int
pageSize)
{
this.inner
= inner;
this.separator
= separator;
this.pageSize
= pageSize;
}
public
void
Export(Student
student)
{
if
(++counter % pageSize == 0)
{
separator.WriteSeparator();
counter
= 0;
}
inner.Export(student);
}
}
Il
test che verifica il comportamento di EportStudentWithSeparator,
continua a passare una volta aggiunto al costruttore il valore 10 per
indicare la dimensione della pagina.
[SetUp]
public
void
SetUp()
{
expoter
= new
Mock<IExportStudent>();
separator
= new
Mock<ISeparator>();
sut
= new
ExportStudentWithSeparator(expoter.Object,
separator.Object,
10);
}
[Test]
public
void
SeparatorTest()
{
var
student = new
Student();
for
(int
i = 0; i < 10; i++)
sut.Export(student);
expoter.Verify(m
=> m.Export(It.IsAny<Student>()),
Times.AtLeast(10));
separator.Verify(m
=> m.WriteSeparator(),Times.Once());
}
Per
questa richiesta, la scelta della composizione ha dato dei vantaggi
sulla evoluzione del codice, è probabile che ulteriori esigenze
possano essere più facilmente esaudite visto che abbiamo separato il
più possibile i comportamenti assegnandoli ad oggetti ben precisi.
Fate
le vostre considerezioni.
Nessun commento:
Posta un commento