Qualcuno potrebbe implementare la soluzione precedente, utilizzando un "Extension Method". Vediamo come rivedere il codice precedente:
public
static
class
ObjectExtensions
{
public
static
T Perform<T>(this
T instance, Action<T>
action)
where
T : class
{
if
(instance != null)
action(instance);
return
instance;
}
}
Con questa "magia" qualsiasi oggetto risulta avere il metodo Perform disponibile e quindi sarà eseguita una azione su quell'oggetto se e solo se quell'oggetto è stato instanziato.
La classe Repository viene aggiornata come segue:
public
class Repository
{
private
StudentsDatabase studentsDatabase;
public
Student
StudentFoundByCode(int
code)
{
return studentsDatabase.GetByCode(code);
}
}
e tramite l'extension method otteniamo una sintassi simile alla precedente:
repository
.StudentFoundByCode(1234)
.Perform(new
Output().Write);
la differenza sostanziale è che siamo ancora in balia della null reference exception, infatti ora restituiamo un oggetto Student che non è più incapsulato nella classe che può o non può eseguire operazioni sull'oggetto student. Ovvero, possiamo a questo punto fare le seguente operazione:
repository
.StudentFoundByCode(1234)
.Name;
Student non è stato isolato. Questa soluzione è caldamente sconsigliabile. Invece possiamo implementare due classi Generic per incapsulare qualsiasi oggetto ed eseguire operazioni safe sui comportamenti dell'oggetto.
Definiamo il contratto per eseguire le operazioni su un oggetto:
public
interface
ISubstitute<out
T>
where
T : class
{
ISubstitute<T>
Perform(Action<T>
action);
}
Creiamo la classe che non esegue operazioni quando l'oggetto non è instanziato:
public
class
NullSubstitute<T>
: ISubstitute<T>
where
T : class
{
public
ISubstitute<T>
Perform(Action<T>
action)
{
return
this;
}
}
Implementiamo la classe che esegue operazioni quando l'oggetto è instanziato:
public
class Substitute<T>
: ISubstitute<T>
where
T : class
{
private
readonly
T instance;
public Substitute(T instance)
{
this.instance
= instance;
}
public
ISubstitute<T>
Perform(Action<T>
action)
{
action(instance);
return
this;
}
}
Aggiorniamo la classe repository:
public
class Repository
{
private
StudentsDatabase studentsDatabase;
public
ISubstitute<Student>
StudentFoundByCode(int
code)
{
var
student = studentsDatabase.GetByCode(code);
if
(student==null)
return
new
NullSubstitute<Student>();
return
new Substitute<Student>(student);
}
}
Ora siamo nella condizione di non dover creare per tutti gli oggetti una specifica classe che si occupa di eseguire operazioni sull'oggetto, per chi ama l'utilizzo dell' extension method possiamo eventualemente creare una extension per centralizzare la factory che è implicita nella parte del codice che decide quale tipo di oggetto Substitute deve essere instanziato:
public
static
class
ObjectExtensions
{
public
static
ISubstitute<T>
CreateSubstitute<T>(this
T instance)
where
T : class
{
if
(instance == null)
return
new
NullSubstitute<T>();
return
new Substitute<T>(instance);
}
}
quindi, aggiorniamo il metodo StudentFoundByCode:
public
ISubstitute<Student>
StudentFoundByCode(int
code)
{
return
_studentsDatabase
.GetByCode(code)
.CreateSubstitute();
}