Quando
progettate il vostro oggetto, pensate sempre che ci sarà qualcuno
che lo utilizzerà al vostro posto. Quindi deve essere il vostro
design a guidare l'utilizzo dell'oggetto.
Vi
elenco una serie di accorgimenti da provare nei vostri prossimi
sviluppi.
Non
ritornate mai un null quando non sapete che valore restituire
public
object
DoSomething()
{
if
(IsOK())
return
new
Object();
return
null;
}
Non
ritornate mai un valore di default quando non sapete che valore
restituire
public
int
DoSomething()
{
if
(IsOK())
return
100;
return
-1;
}
Non
ritornate null quando dovete restituire una lista
public
object[]
DoSomething()
{
if
(IsOK())
return
new[]{new
object(),new
object(),new
object()
};
return
null;
}
Non
usare eccezioni se non sono realmente necessarie
public
object
DoSomething()
{
if
(IsOK())
return
new
Object();
throw
new
Exception();
}
Per
ovviare a queste scelte ci sono vari approcci.
Potete
creare un oggetto che incapsula il valore di ritorno e che può
eseguire operazioni se il valore di ritorno è valido.
Esempio:
tutti
i casi precedenti si possono risolvere con una sintassi simile alla
seguente
public
IDelegate<object>
DoSomething()
{
if
(IsOK())
return
new
Delegate<object>(new
object());
return
new
NoDelegate<object>();
}
Vediamo
in pratica come si usano:
public
class Main
: IPerform<object>
{
public
Main()
{
DoSomething().Perform(this);
}
public
void
Execute(object
value)
{
Console.WriteLine(value);
}
}
Queste
le implementazioni delle due tipologie di Delegate
public interface IPerform<in T>
{
void
Execute(T value);
}
public
interface
IDelegate<out
T>
{
void
Perform(IPerform<T>
perform);
}
public
class
Delegate<T>
: IDelegate<T>
{
private
readonly
T value;
public
Delegate(T value)
{
this.value
= value;
}
public
void
Perform(IPerform<T>
perform)
{
perform.Execute(value);
}
}
public
class
NoDelegate<T>
: IDelegate<T>
{
public
void
Perform(IPerform<T>
perform) { }
}
Per
renderla più snella, vi consiglio di ovviare la scelta
dell'interfaccia IPerform ed usare una Action
public
class
Main
{
public
Main()
{
DoSomething().Perform(value
=> Console.WriteLine(value));
}
}
Quando
le opzioni di ritorno ha più casi della semplice combo positivo/negativo,
potete esporre tutte le opzioni possibili nell'interfaccia:
public
IDelegate<object>
DoSomething()
{
switch
(GetStatus())
{
case
1:
return
new
Delegate1<object>(new
object());
case
2:
return
new
Delegate2<object>(new
object());
case
3:
return
new
Delegate2<object>(new
object());
}
return
new
Delegate<object>();
}
public
class
Main
{
public
Main()
{
var
result = DoSomething();
result.DoCase1(value
=> Console.WriteLine("case1:"
+ value));
result.DoCase2(value
=> Console.WriteLine("case2:"
+ value));
result.DoCase3(value
=> Console.WriteLine("case3:"
+ value));
}
}
ecco
una possibile implementazione:
public
interface
IDelegate<out
T>
{
void
DoCase1(Action<T>
perform);
void
DoCase2(Action<T>
perform);
void
DoCase3(Action<T>
perform);
}
public
class
Delegate<T>
: IDelegate<T>
{
public
virtual
void
DoCase1(Action<T>
perform) { }
public
virtual
void
DoCase2(Action<T>
perform) { }
public
virtual
void
DoCase3(Action<T>
perform) { }
}
public
class
Delegate1<T>
: Delegate<T>
{
private
readonly
T value;
public
Delegate1(T value) { this.value
= value; }
public
override
void
DoCase1(Action<T>
perform) { perform(value); }
}
public
class
Delegate2<T>
: Delegate<T>
{
private
readonly
T value;
public
Delegate2(T value) { this.value
= value; }
public
override
void
DoCase2(Action<T>
perform) { perform(value); }
}
public
class
Delegate3<T>
: Delegate<T>
{
private
readonly
T value;
public
Delegate3(T value) { this.value
= value; }
public
override
void
DoCase3(Action<T>
perform) { perform(value); }
}
Questa
strada lascia aperta la possibilità all'utilizzatore di non
dichiarare le callback da usare in tutti i casi. Quindi in questo
caso potete implementare un builder per obbligare la definizione
delle varie Action, oppure obbligare l'implementazione di una
interfaccia con le tre callback. Vediamo queste due soluzioni nel
prossimo post.
Nessun commento:
Posta un commento