Quando
parliamo di oop la maggior parte dei programmatori cita
"l'ereditarietà" come caratteristica principale di questo
paradigma. Al contrario l'ereditarietà è solo una delle possibilità
che può indurre in errori e nella stesura di codice poco leggibile.
Facciamo un esempio:
"Vogliamo
creare tre classi che esportano una lista di studenti a video, csv,
xml con una caratteristica in comune ogni 10 elementi deve essere
inserito un separatore per creare una sorta di paginazione."
Cerchiamo
di esaudire questa richiesta passando per la strada dell'ereditarietà Per fare questo scriviamo la classe base che
contiene il comportamento comune.
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();
}
public
class ExportStudentsToVideo
:
ExportStudentsWithSeparatorEachTenLines
{
protected
override
void
Export(Student
student)
{
Console.WriteLine(student.ToString());
}
protected
override
void
WriteSeparator()
{
Console.WriteLine("");
}
}
Ragioniamo
su quanti comportamenti ci sono nella storia che dobbiamo realizzare
:
- dobbiamo esportare in vari formati la lista degli studenti;
- dobbiamo esportare le informazioni di uno studente in una riga;
- dobbiamo per ogni tipo di formato poter inserire un separatore per creare una paginazione;
- dobbiamo esportare gli studenti a gruppi di 10 elementi;
Scriviamo
il primo test:
[SetUp]
public
void
SetUp()
{
expoter
= new
Mock<IExportStudent>();
sut
= new
ExporterStudentsList(expoter.Object);
}
[Test]
public
void
ExportStudentListTest()
{
var
student = new
Student();
sut.Export(new[]
{student});
expoter.Verify(m
=> m.Export(It.IsAny<Student>()));
}
Definiamo
la classe ExporterStudentsList che ha il compito di esportare la
lista di studenti senza nessun'altro comportamento aggiuntivo.
public
class ExporterStudentsList
{
private
IExportStudent
exporter;
public
ExporterStudentsList(IExportStudent
exporter)
{
this.exporter
= exporter;
}
public
void
Export(Student[]
students)
{
foreach
(var
student in
students)
exporter.Export(student);
}
}
public
interface IExportStudent
{
void
Export(Student
student);
}
Ora
creiamo la classe che esporta le informazioni dello studente a video.
public
class ExportStudentToVideo
: IExportStudent
{
public
void
Export(Student
student)
{
Console.WriteLine(string.Format("Studente
{0} {1}",
student.Name,
student.Lastname));
}
}
Bene,
ora passiamo all'aggiunta del comportamento che inserisce un
separatore ogni 10 elementi.
Scriviamo
il test:
[SetUp]
public
void
SetUp()
{
expoter
= new
Mock<IExportStudent>();
separator
= new
Mock<ISeparator>();
sut
= new
ExportStudentWithSeparator(expoter.Object,
separator.Object);
}
[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());
}
public
class
ExportStudentWithSeparator
: IExportStudent
{
private
int
linesCounter;
private
IExportStudent
inner;
private
ISeparator
separator;
private
byte
counter;
public
ExportStudentWithSeparator(IExportStudent
inner,
ISeparator
separator)
{
this.inner
= inner;
this.separator
= separator;
}
public
void
Export(Student
student)
{
if
(++counter%10 == 0)
{
separator.WriteSeparator();
counter
= 0;
}
inner.Export(student);
}
}
public
interface
ISeparator
{
void
WriteSeparator();
}
public
class
SeparatorToVideo
: ISeparator
{
public
void
WriteSeparator()
{
Console.WriteLine(string.Empty);
}
}
var
arrayOfStudents = new[]
{
new
Student(),
new
Student(),
new
Student()
};
//Ereditarietà
new
ExportStudentsToVideo()
.Save(arrayOfStudents);
//
VS
//Composizione
new
ExporterStudentsList(
new
ExportStudentWithSeparator(
new
ExportStudentToVideo(),
new
SeparatorToVideo()))
.Export(arrayOfStudents);
- Usando l'ereditarietà il codice di produzione sembra più semplice, in realtà risulta essere solo più compatto;
- Non sappiamo esattamente cosa fa la classe ExportStudentsToVideo invece la controparte tramite la sua verbosità ci permette di capire quali comportamenti sono in gioco;
- Con l'ereditarietà non potevamo scrivere test semplici (infatti non ho provato neppure a farlo);
- le ulteriori richieste del product owner potrebbero essere un po' complicate da risolvere;
Nessun commento:
Posta un commento