Qualche volta sarà capitato di dover
sapere se la chiamata di un metodo è andata a buon fine, oppure un metodo potrebbe restituirvi
stati diversi in base alle operazioni che può eseguire internamente.
Normalmente potete restituire un
booleano oppure un enum per indicare lo stato, esempio:
public
bool
DoSomething()
{
//
...
}
oppure
public
Status DoSomething()
{
//
...
}
con
Status così definito:
public
enum Status
{
High,
Medium
}
Pensate
al caso che l'operazione che invocate oltre ad indicare il suo esito
dovesse restituire un risultato. Esempio il TryParse di .NET:
int
result;
if
(int.TryParse("1",
out
result))
{
Console.WriteLine("Il
numero è {0}",
result);
}
else
{
Console.WriteLine("La
stringa passata non è un numero");
}
Se
poi pensate al caso di stati multipli in base al valore dopo siete
costretti ad inserire il risultato dentro uno switch per decidere che
operazione eseguire:
switch
(new Buzz().DoSomething())
{
case
Status.High:
Console.WriteLine("Level
100");
break;
case
Status.Low:
Console.WriteLine("Level
0");
break;
case
Status.Medium:
Console.WriteLine("Level
50");
break;
default:
throw
new
InvalidOperationException();
}
Come
potete notare ci sono un po' di problemini a gestire una situazione
di questo tipo. Ora
vi chiedo, non sarebbe più semplice avere una sintassi di questo
tipo?
new
Buzz()
.DoSomething()
.Accept(new
StatusVisitor());
dove
la class StatusVisitor è definita come:
public
class
StatusVisitor
: IVisitor
{
public
void
Visit(Low
status)
{
Console.WriteLine("Level
0");
}
public
void
Visit(High
status)
{
Console.WriteLine("Level
100");
}
public
void
Visit(Medium
status)
{
Console.WriteLine("Level
50");
}
}
e
quindi, in base al risultato di DoSomething viene eseguita una delle
tre istruzioni sopra indicate. Per gestire il tutto dobbiamo creare
la seguente struttura:
public
class Buzz
{
//
per ora impostiamo Medium come valore di default
public
IStatus
DoSomething()
{
return
new
Medium();
}
}
//
creiamo l'interfaccia status che accetta di essere visitata da un
// visitor
public
interface IStatus
{
void
Accept(IVisitor
visitor);
}
//
di seguito implementiamo i tre stati
public
class Medium
: IStatus
{
public
void
Accept(IVisitor
visitor)
{
visitor.Visit(this);
}
}
public
class
High
: IStatus
{
public
void
Accept(IVisitor
visitor)
{
visitor.Visit(this);
}
}
public
class
Low
: IStatus
{
public
void
Accept(IVisitor
visitor)
{
visitor.Visit(this);
}
}
//
per finire definiamo un'interfaccia che conosce tutti gli stati
public
interface IVisitor
{
void
Visit(Low
status);
void
Visit(High
status);
void
Visit(Medium
status);
}
A
questo punto quando eseguiamo:
new
Buzz()
.DoSomething()
.Accept(new
StatusVisitor());
l'output
è: Level 50
Se
non vogliamo creare una classe StatusVisitor che si occupa di gestire
l'output possiamo demandare anche al chiamante l'onere di gestire
l'output come nel caso dello switch:
public
class Main
: IVisitor
{
public
void
main()
{
new
Buzz()
.DoSomething()
.Accept(this);
}
void
IVisitor.Visit(Low
status)
{
Console.WriteLine("Level
0");
}
void
IVisitor.Visit(High
status)
{
Console.WriteLine("Level
100");
}
void
IVisitor.Visit(Medium
status)
{
Console.WriteLine("Level
50");
}
}
Se
avete la necessità di produrre anche dei dati che vanno propagati oltre allo
stato, basta aggiungere tali informazioni alle varie classi che
derivano da IStatus.
Per
chiarire le idee, vediamo come poteva essere scritto il TryParse seguendo
il pattern appena spiegato:
//
definiamo la nostra class Int
public
static
class
Int
{
public
static
IResult
TryParse(string
value)
{
return
IsNumber(value)
? (IResult)new
IsNumber(Convert.ToInt32(value))
:
new
IsNotNumber();
}
}
//
definiamo l'interfaccia per accetare i visitatori
public
interface
IResult
{
void
Accept(INumberVisitor
visitor);
}
//
definiamo l'interfaccia che conosce gli stati IsNumber e
// IsNotNumber
public
interface
INumberVisitor
{
void
Visit(IsNumber
result);
void
Visit(IsNotNumber
result);
}
public
class
IsNotNumber
: IResult
{
public
void
Accept(INumberVisitor
visitor)
{
visitor.Visit(this);
}
}
public
class
IsNumber
: IResult
{
public
int
Value { get;
private
set;
}
public
IsNumber(int
value)
{
Value
= value;
}
public
void
Accept(INumberVisitor
visitor)
{
visitor.Visit(this);
}
}
ora proviamo il tutto
public
class Main
: INumberVisitor
{
public
void
main()
{
Int.TryParse("1").Accept(this);
}
void
INumberVisitor.Visit(IsNumber
result)
{
Console.WriteLine("Il
numero è {0}",result.Value);
}
void
INumberVisitor.Visit(IsNotNumber
result)
{
Console.WriteLine("La
stringa passata non è un numero");
}
}
contro:
public
class
Main
{
public
void
main()
{
int
result;
if
(int.TryParse("1",
out
result))
{
Console.WriteLine("Il
numero è {0}",
result);
}
else
{
Console.WriteLine("La
stringa passata non è un numero");
}
}
Molto elegante!
RispondiElimina