Il presente articolo è stato tradotto automaticamente.
Programmazione orientata ad aspetti
Programmazione orientata ad aspetti con la classe RealProxy
Un'applicazione ben progettata ha livelli separati così diverse preoccupazioni non interagiscono più del necessario. Immaginare si sta progettando un'applicazione loosely coupled e gestibile, ma nel mezzo di sviluppo, vedere alcuni requisiti che potrebbero non adattarsi bene nell'architettura, come ad esempio:
- L'applicazione deve avere un sistema di autenticazione utilizzato prima di qualsiasi query o aggiornamento.
- I dati devono essere convalidati prima di essere scritte nel database.
- L'applicazione deve disporre di controllo e registrazione per le operazioni sensibili.
- L'applicazione deve mantenere un registro di debug per controllare se le operazioni sono OK.
- Alcune operazioni devono avere le loro prestazioni misurate per vedere se sono nell'intervallo desiderato.
Qualcuno di questi requisiti è necessario un sacco di lavoro e, più di questo, la duplicazione del codice. È necessario aggiungere il codice stesso in molte parti del sistema, che va contro la "non ripetere se stessi" principio (a secco) e rende più difficile la manutenzione. Qualsiasi modifica requisito provoca un enorme cambiamento nel programma. Quando devo aggiungere qualcosa come quella nelle mie applicazioni, penso, "perché non è il compilatore aggiungere questo ripetuto codice in più punti per me?" oppure "Vorrei avere qualche opzione per 'Registrazione Add a questo metodo.'"
La buona notizia è che qualcosa di simile che esiste: programmazione orientata agli aspetti (AOP). Codice generale separa dagli aspetti che travalicano i confini di un oggetto o un livello. Per esempio, il registro dell'applicazione non è legato a qualsiasi livello di applicazione. Esso si applica a tutto il programma e dovrebbe essere presente ovunque. Che ha chiamato una preoccupazione trasversale.
AOP è, secondo Wikipedia, "un paradigma di programmazione che mira ad aumentare la modularità consentendo la separazione del crosscutting problemi." Si tratta di funzionalità che si verifica in più parti del sistema e la separa dal nucleo dell'applicazione, migliorando così la separazione delle problematiche, evitando la duplicazione del codice e accoppiamento.
In questo articolo, sarò spiegare le basi di AOP e poi dettaglio come renderlo più facile utilizzando un proxy dinamico tramite la classe RealProxy di Microsoft .NET Framework.
Implementazione di AOP
Il più grande vantaggio di AOP è che hai solo preoccuparsi dell'aspetto in un unico luogo, programmazione e una volta e la sua applicazione in tutti i luoghi dove necessario. Ci sono molti usi per AOP, quali:
- Effettuando la registrazione nell'applicazione.
- Utilizzando l'autenticazione prima di un'operazione (ad esempio permettendo alcune operazioni solo per gli utenti autenticati).
- Implementazione di convalida o notifica per l'impostazione delle proprietà (chiamando l'evento PropertyChanged quando una proprietà è stata modificata per le classi che implementano l'interfaccia INotifyPropertyChanged).
- Modifica del comportamento di alcuni metodi.
Come potete vedere, AOP ha molti usi, ma esso deve maneggiare con cura. Si conserva qualche codice fuori dalla tua vista, ma è ancora lì, in esecuzione in ogni chiamata dove è presente l'aspetto. Può avere bug e gravemente influire sulle prestazioni dell'applicazione. Un sottile bug nell'aspetto potrebbe costare molte ore di debug. Se il tuo aspetto non è utilizzato in molti luoghi, a volte è meglio aggiungerlo direttamente al codice.
AOP implementazioni utilizzano alcune tecniche comuni:
- Aggiunta di codice sorgente utilizzando un pre-processore, come quella di C++.
- Utilizzando un post-processore per aggiungere le istruzioni sul codice binario compilato.
- Usando un compilatore speciale che aggiunge il codice durante la compilazione.
- Utilizzando un intercettore di codice in fase di esecuzione che intercetta l'esecuzione e aggiunge il codice desiderato.
In .NET Framework, il più comunemente usato di queste tecniche è post-elaborazione e codice di intercettazione. Il primo è la tecnica utilizzata da PostSharp (postsharp.net) e quest'ultima viene utilizzata dai contenitori di iniezione (DI) dipendenza come castello DynamicProxy (bit.ly/JzE631) e l'unità (unity.codeplex.com). Questi strumenti di solito usano un modello di progettazione denominato decoratore o Proxy per effettuare l'intercettazione del codice.
Il modello di progettazione decoratore
Il design pattern Decorator risolve un problema comune: Avete una classe e desidera aggiungere alcune funzionalità ad esso. Avete diverse possibilità per questo:
- È possibile aggiungere nuove funzionalità alla classe direttamente. Tuttavia, che dà la classe un'altra responsabilità e fa male il principio della "responsabilità singola".
- Si potrebbe creare una nuova classe che esegue questa funzionalità e chiamarlo dalla vecchia classe. Questo porta un nuovo problema: Se si desidera utilizzare la classe senza la nuova funzionalità?
- Si potrebbe ereditare una classe nuova e aggiungere nuove funzionalità, ma che può provocare molte nuove classi. Per esempio, diciamo che sono una classe di repository per creare, leggere, aggiornare e cancellare le operazioni del database (CRUD) e si desidera aggiungere il controllo. Successivamente, si desidera aggiungere la convalida dei dati per verificare che i dati viene aggiornati correttamente. Dopo di che, si potrebbe anche voler autenticare l'accesso per assicurare che solo gli utenti autorizzati possono accedere alle classi. Questi sono i grandi temi: Si potrebbero avere alcune classi che implementano tutti e tre gli aspetti, e alcune implementano solo due di loro, o anche solo uno. Quante classi finirebbe per avere?
- Si può "decorare" la classe con l'aspetto, creando una nuova classe che utilizza l'aspetto e quindi chiama quello vecchio. In questo modo, se avete bisogno di un aspetto, decorare una volta. Per due aspetti, decorare due volte e così via. Diciamo che si ordina un giocattolo (come siamo tutti geek, una Xbox o uno smartphone è OK). Ha bisogno di un pacchetto per la visualizzazione in archivio e per la protezione. Poi, ordinarlo con regalo, la seconda decorazione, per impreziosire la scatola con nastri, strisce, carte e carta da regalo. Il negozio invia il giocattolo con un terzo pacchetto, una scatola con palle di polistirolo per protezione. Avete tre decorazioni, ognuno con una diversa funzionalità, ogni uno indipendente e uno da altro. Puoi acquistare il tuo giocattolo con nessuna confezione regalo, ritirarlo presso il negozio senza il box esterno o anche comprarlo con senza scatola (con uno sconto speciale!). Si può avere il tuo giocattolo con qualsiasi combinazione delle decorazioni, ma non cambiano le sue funzionalità di base.
Ora che conosci il pattern Decorator, ti mostrerò come implementarlo in c#.
Innanzitutto, creare un'interfaccia IRepository < T >:
public interface IRepository<T>
{
void Add(T entity);
void Delete(T entity);
void Update(T entity);
IEnumerable<T> GetAll();
T GetById(int id);
}
Implementarlo con il Repository < T > classe, illustrata Figura 1.
Figura 1 il Repository < T > Classe
public class Repository<T> : IRepository<T>
{
public void Add(T entity)
{
Console.WriteLine("Adding {0}", entity);
}
public void Delete(T entity)
{
Console.WriteLine("Deleting {0}", entity);
}
public void Update(T entity)
{
Console.WriteLine("Updating {0}", entity);
}
public IEnumerable<T> GetAll()
{
Console.WriteLine("Getting entities");
return null;
}
public T GetById(int id)
{
Console.WriteLine("Getting entity {0}", id);
return default(T);
}
}
Utilizzare il Repository < T > classe per aggiungere, aggiornare, cancellare e recuperare gli elementi della classe Customer:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
Il programma potrebbe apparire qualcosa di simile Figura 2.
Figura 2 il programma principale, con nessuna registrazione
static void Main(string[] args)
{
Console.WriteLine("***\r\n Begin program - no logging\r\n");
IRepository<Customer> customerRepository =
new Repository<Customer>();
var customer = new Customer
{
Id = 1,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nEnd program - no logging\r\n***");
Console.ReadLine();
}
Quando si esegue questo codice, vedrete qualcosa di simile Figura 3.
Figura 3 Output del programma con nessuna registrazione
Immaginate il che vostro capo vi chiede di aggiungere la registrazione a questa classe. È possibile creare una nuova classe che decorerà IRepository < T >. Riceve la classe per costruire e implementa l'interfaccia stessa, come mostrato Figura 4.
Figura 4 il Repository di Logger
public class LoggerRepository<T> : IRepository<T>
{
private readonly IRepository<T> _decorated;
public LoggerRepository(IRepository<T> decorated)
{
_decorated = decorated;
}
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public void Add(T entity)
{
Log("In decorator - Before Adding {0}", entity);
_decorated.Add(entity);
Log("In decorator - After Adding {0}", entity);
}
public void Delete(T entity)
{
Log("In decorator - Before Deleting {0}", entity);
_decorated.Delete(entity);
Log("In decorator - After Deleting {0}", entity);
}
public void Update(T entity)
{
Log("In decorator - Before Updating {0}", entity);
_decorated.Update(entity);
Log("In decorator - After Updating {0}", entity);
}
public IEnumerable<T> GetAll()
{
Log("In decorator - Before Getting Entities");
var result = _decorated.GetAll();
Log("In decorator - After Getting Entities");
return result;
}
public T GetById(int id)
{
Log("In decorator - Before Getting Entity {0}", id);
var result = _decorated.GetById(id);
Log("In decorator - After Getting Entity {0}", id);
return result;
}
}
Questa nuova classe avvolge i metodi per la classe decorata e aggiunge la funzionalità di registrazione. È necessario modificare il codice per chiamare la classe di registrazione, come mostrato Figura 5.
Figura 5 il programma principale tramite il Repository di Logger
static void Main(string[] args)
{
Console.WriteLine("***\r\n Begin program - logging with decorator\r\n");
// IRepository<Customer> customerRepository =
// new Repository<Customer>();
IRepository<Customer> customerRepository =
new LoggerRepository<Customer>(new Repository<Customer>());
var customer = new Customer
{
Id = 1,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nEnd program - logging with decorator\r\n***");
Console.ReadLine();
}
È sufficiente creare la nuova classe, passando un'istanza della classe come parametro per il costruttore. Quando si esegue il programma, si può vedere ha la registrazione, come mostrato Figura 6.
Figura 6 esecuzione del programma di registrazione con un decoratore
Si potrebbe pensare: "OK, l'idea è buona, ma è un sacco di lavoro: Devo implementare tutte le classi e aggiungere l'aspetto di tutti i metodi. Che sarà difficile da mantenere. C'è un altro modo per farlo? " Con .NET Framework, può utilizzare la reflection per ottenere tutti i metodi e di eseguirli. La libreria di classi base (BCL) ha anche la classe RealProxy (bit.ly/18MfxWo) che fa l'implementazione per voi.
Creazione di un Proxy dinamico con RealProxy
Classe RealProxy offre funzionalità di base per i proxy. È una classe astratta che deve essere ereditata sottoposto a override il metodo Invoke e aggiungendo nuove funzionalità. Questa classe è lo spazio dei nomi System. Per creare un proxy dinamico, utilizzare codice simile a Figura 7.
Figura 7 la classe Proxy dinamico
class DynamicProxy<T> : RealProxy
{
private readonly T _decorated;
public DynamicProxy(T decorated)
: base(typeof(T))
{
_decorated = decorated;
}
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
Log("In Dynamic Proxy - Before executing '{0}'",
methodCall.MethodName);
try
{
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
Log("In Dynamic Proxy - After executing '{0}' ",
methodCall.MethodName);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
Log(string.Format(
"In Dynamic Proxy- Exception {0} executing '{1}'", e),
methodCall.MethodName);
return new ReturnMessage(e, methodCall);
}
}
}
Nel costruttore della classe, è necessario chiamare il costruttore della classe base, passando il tipo della classe per essere decorato. Quindi eseguire l'override del metodo Invoke che riceve un parametro IMessage. Contiene un dizionario con tutti i parametri passati per il metodo. Il parametro IMessage è typecast a un IMethodCallMessage, così è possibile estrarre il parametro MethodBase (che ha il tipo MethodInfo).
I passi successivi sono per aggiungere l'aspetto desiderato prima di chiamare il metodo, chiamare il metodo originale con MethodInfo e quindi aggiungere l'aspetto dopo la chiamata.
Non è possibile chiamare il proxy direttamente, perché DynamicProxy < T > non è un IRepository < cliente >. Ciò significa che non puoi chiamarlo come questo:
IRepository<Customer> customerRepository =
new DynamicProxy<IRepository<Customer>>(
new Repository<Customer>());
Per utilizzare il repository decorato, è necessario utilizzare il metodo GetTransparentProxy, che restituisce un'istanza di IRepository < cliente >. Ogni metodo di questa istanza che viene chiamata passerà attraverso il metodo Invoke del proxy. Per facilitare questo processo, è possibile creare una classe Factory per creare il proxy e restituire l'istanza per il repository:
public class RepositoryFactory
{
public static IRepository<T> Create<T>()
{
var repository = new Repository<T>();
var dynamicProxy = new DynamicProxy<IRepository<T>>(repository);
return dynamicProxy.GetTransparentProxy() as IRepository<T>;
}
}
In questo modo, il programma principale sarà simile a Figura 8.
Figura 8 il programma principale con un Proxy dinamico
static void Main(string[] args)
{
Console.WriteLine("***\r\n Begin program - logging with dynamic proxy\r\n");
// IRepository<Customer> customerRepository =
// new Repository<Customer>();
// IRepository<Customer> customerRepository =
// new LoggerRepository<Customer>(new Repository<Customer>());
IRepository<Customer> customerRepository =
RepositoryFactory.Create<Customer>();
var customer = new Customer
{
Id = 1,
Name = "Customer 1",
Address = "Address 1"
;
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nEnd program - logging with dynamic proxy\r\n***");
Console.ReadLine();
}
Quando si esegue questo programma, si ottiene un risultato simile come prima, come mostrato Figura 9.
Esecuzione del programma figura 9 con Proxy dinamico
Come potete vedere, hai creato un proxy dinamico che consente di aggiungere aspetti al codice, senza bisogno di ripeterlo. Se si desidera aggiungere un nuovo aspetto, sarebbe necessario solo creare una nuova classe, ereditano da RealProxy e utilizzarlo per decorare il primo proxy.
Se il vostro capo torna a voi e vi chiede di aggiungere l'autorizzazione al codice, così solo gli amministratori possono accedere al repository, è possibile creare un nuovo proxy come indicato in Figura 10.
Figura 10 un Proxy di autenticazione
class AuthenticationProxy<T> : RealProxy
{
private readonly T _decorated;
public AuthenticationProxy(T decorated)
: base(typeof(T))
{
_decorated = decorated;
}
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
if (Thread.CurrentPrincipal.IsInRole("ADMIN"))
{
try
{
Log("User authenticated - You can execute '{0}' ",
methodCall.MethodName);
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
Log(string.Format(
"User authenticated - Exception {0} executing '{1}'", e),
methodCall.MethodName);
return new ReturnMessage(e, methodCall);
}
}
Log("User not authenticated - You can't execute '{0}' ",
methodCall.MethodName);
return new ReturnMessage(null, null, 0,
methodCall.LogicalCallContext, methodCall);
}
}
La fabbrica di repository deve essere modificata per chiamare entrambi proxy, come mostrato Figura 11.
Figura 11 fabbrica Repository decorata da due procure
public class RepositoryFactory
{
public static IRepository<T> Create<T>()
{
var repository = new Repository<T>();
var decoratedRepository =
(IRepository<T>)new DynamicProxy<IRepository<T>>(
repository).GetTransparentProxy();
// Create a dynamic proxy for the class already decorated
decoratedRepository =
(IRepository<T>)new AuthenticationProxy<IRepository<T>>(
decoratedRepository).GetTransparentProxy();
return decoratedRepository;
}
}
Quando si modifica il programma principale per Figura 12 ed eseguirlo, si otterrà l'output mostrato Figura 13.
Figura 12 il programma principale chiamata Repository con due utenti
static void Main(string[] args)
{
Console.WriteLine(
"***\r\n Begin program - logging and authentication\r\n");
Console.WriteLine("\r\nRunning as admin");
Thread.CurrentPrincipal =
new GenericPrincipal(new GenericIdentity("Administrator"),
new[] { "ADMIN" });
IRepository<Customer> customerRepository =
RepositoryFactory.Create<Customer>();
var customer = new Customer
{
Id = 1,
Name = "Customer 1",
Address = "Address 1"
};
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine("\r\nRunning as user");
Thread.CurrentPrincipal =
new GenericPrincipal(new GenericIdentity("NormalUser"),
new string[] { });
customerRepository.Add(customer);
customerRepository.Update(customer);
customerRepository.Delete(customer);
Console.WriteLine(
"\r\nEnd program - logging and authentication\r\n***");
Console.ReadLine();
}
Figura 13 Output del programma utilizzando due procure
Il programma esegue i metodi di deposito due volte. La prima volta, esso viene eseguito come utente admin e vengono chiamati i metodi. La seconda volta, funziona come un normale utente e i metodi vengono ignorati.
Che è molto più facile, no? Si noti che la factory restituisce un'istanza di IRepository < T >, così il programma non sa se sta usando la versione decorata. Ciò rispetta il principio di sostituzione di Liskov, che dice che se S è un sottotipo di T, gli oggetti di tipo T possono essere sostituiti con oggetti di tipo S. In questo caso, utilizzando un IRepository < cliente > interfaccia, è possibile utilizzare qualsiasi classe che implementa questa interfaccia con nessun cambiamento nel programma.
Funzioni di filtraggio
Fino ad ora, non non c'era nessun filtraggio nelle funzioni; l'aspetto è applicato a ogni metodo di classe chiamato. Spesso questo non è il comportamento desiderato. Ad esempio, si potrebbe non voler accedere i metodi di recupero (GetAll e GetById). Un modo per compire questo è filtrare l'aspetto di nome, come in Figura 14.
Nella figura 14 metodi di filtraggio per l'aspetto
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
if (!methodInfo.Name.StartsWith("Get"))
Log("In Dynamic Proxy - Before executing '{0}'",
methodCall.MethodName);
try
{
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
if (!methodInfo.Name.StartsWith("Get"))
Log("In Dynamic Proxy - After executing '{0}' ",
methodCall.MethodName);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
if (!methodInfo.Name.StartsWith("Get"))
Log(string.Format(
"In Dynamic Proxy- Exception {0} executing '{1}'", e),
methodCall.MethodName);
return new ReturnMessage(e, methodCall);
}
}
Il programma controlla se il metodo inizia con "Get". In questo caso il programma non si applica l'aspetto. Questo funziona, ma il codice di filtro viene ripetuto tre volte. Inoltre, il filtro è all'interno del proxy, che ti permetterà di cambiare la classe ogni volta che si desidera modificare il proxy. È possibile migliorare questo creando un predicato di IsValidMethod:
private static bool IsValidMethod(MethodInfo methodInfo)
{
return !methodInfo.Name.StartsWith("Get");
}
Ora dovete fare la modifica in un solo luogo, ma è ancora necessario modificare la classe ogni volta che si desidera cambiare il filtro. Una soluzione a questo sarebbe per esporre il filtro come una proprietà della classe, quindi è possibile assegnare la responsabilità della creazione di un filtro al chiamante. È possibile creare una proprietà di filtro di tipo predicato < MethodInfo > e usarlo per filtrare i dati, come illustrato nel Figura 15.
Figura 15 filtro Proxy
class DynamicProxy<T> : RealProxy
{
private readonly T _decorated;
private Predicate<MethodInfo> _filter;
public DynamicProxy(T decorated)
: base(typeof(T))
{
_decorated = decorated;
_filter = m => true;
}
public Predicate<MethodInfo> Filter
{
get { return _filter; }
set
{
if (value == null)
_filter = m => true;
else
_filter = value;
}
}
private void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
if (_filter(methodInfo))
Log("In Dynamic Proxy - Before executing '{0}'",
methodCall.MethodName);
try
{
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
if (_filter(methodInfo))
Log("In Dynamic Proxy - After executing '{0}' ",
methodCall.MethodName);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
if (_filter(methodInfo))
Log(string.Format(
"In Dynamic Proxy- Exception {0} executing '{1}'", e),
methodCall.MethodName);
return new ReturnMessage(e, methodCall);
}
}
}
La proprietà Filter è inizializzata con filtro = m = > true. Ciò significa che non non c'è nessun filtro attivo. Quando si assegna la proprietà Filter, il programma verifica se il valore è null, e, in caso affermativo, si resetta il filtro. L'esecuzione del metodo Invoke, il programma controlla il risultato del filtro e, se è vero, si applica l'aspetto. Ora la creazione di proxy nella classe fabbrica assomiglia a questo:
public class RepositoryFactory
{
public static IRepository<T> Create<T>()
{
var repository = new Repository<T>();
var dynamicProxy = new DynamicProxy<IRepository<T>>(repository)
{
Filter = m => !m.Name.StartsWith("Get")
};
return dynamicProxy.GetTransparentProxy() as IRepository<T>;
}
}
}
La responsabilità della creazione del filtro è stata trasferita alla fabbrica. Quando si esegue il programma, si dovrebbe ottenere qualcosa di simile Figura 16.
Figura 16 Output con un Proxy filtrato
Si noti che nel Figura 16 che ultimi due metodi, GetAll e GetById (rappresentata da "Entità guida" e "sempre entità 1") non hanno la registrazione intorno a loro. È possibile migliorare ulteriormente la classe esponendo gli aspetti come eventi. In questo modo, non dovete modificare la classe ogni volta che si desidera modificare l'aspetto. Questo è mostrato Figura 17.
Figura 17 flessibile Proxy
class DynamicProxy<T> : RealProxy
{
private readonly T _decorated;
private Predicate<MethodInfo> _filter;
public event EventHandler<IMethodCallMessage> BeforeExecute;
public event EventHandler<IMethodCallMessage> AfterExecute;
public event EventHandler<IMethodCallMessage> ErrorExecuting;
public DynamicProxy(T decorated)
: base(typeof(T))
{
_decorated = decorated;
Filter = m => true;
}
public Predicate<MethodInfo> Filter
{
get { return _filter; }
set
{
if (value == null)
_filter = m => true;
else
_filter = value;
}
}
private void OnBeforeExecute(IMethodCallMessage methodCall)
{
if (BeforeExecute != null)
{
var methodInfo = methodCall.MethodBase as MethodInfo;
if (_filter(methodInfo))
BeforeExecute(this, methodCall);
}
}
private void OnAfterExecute(IMethodCallMessage methodCall)
{
if (AfterExecute != null)
{
var methodInfo = methodCall.MethodBase as MethodInfo;
if (_filter(methodInfo))
AfterExecute(this, methodCall);
}
}
private void OnErrorExecuting(IMethodCallMessage methodCall)
{
if (ErrorExecuting != null)
{
var methodInfo = methodCall.MethodBase as MethodInfo;
if (_filter(methodInfo))
ErrorExecuting(this, methodCall);
}
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
OnBeforeExecute(methodCall);
try
{
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
OnAfterExecute(methodCall);
return new ReturnMessage(
result, null, 0, methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
OnErrorExecuting(methodCall);
return new ReturnMessage(e, methodCall);
}
}
}
In Figura 17, tre eventi, BeforeExecute, AfterExecute ed ErrorExecuting, sono chiamati dai metodi OnBeforeExecute, OnAfterExecute e OnErrorExecuting. Questi metodi di verificare se il gestore di evento è definito e, se lo è, si verifica se il metodo chiamato passa il filtro. Se è così, lo chiamano il gestore di eventi che applica l'aspetto. La classe factory ora diventa qualcosa di simile Figura 18.
Figura 18 A Repository Factory che imposta l'aspetto eventi e filtro
public class RepositoryFactory
{
private static void Log(string msg, object arg = null)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg, arg);
Console.ResetColor();
}
public static IRepository<T> Create<T>()
{
var repository = new Repository<T>();
var dynamicProxy = new DynamicProxy<IRepository<T>>(repository);
dynamicProxy.BeforeExecute += (s, e) => Log(
"Before executing '{0}'", e.MethodName);
dynamicProxy.AfterExecute += (s, e) => Log(
"After executing '{0}'", e.MethodName);
dynamicProxy.ErrorExecuting += (s, e) => Log(
"Error executing '{0}'", e.MethodName);
dynamicProxy.Filter = m => !m.Name.StartsWith("Get");
return dynamicProxy.GetTransparentProxy() as IRepository<T>;
}
}
Ora avete una classe proxy flessibile, ed è possibile scegliere gli aspetti da applicare prima dell'esecuzione, dopo l'esecuzione, o quando c'è un errore e solo per i metodi selezionati. Con questo, è possibile applicare molti aspetti nel codice con nessuna ripetizione, in modo semplice.
Non una sostituzione
Con AOP è possibile aggiungere codice per tutti i livelli dell'applicazione in modo centralizzato, senza necessità di ripetere il codice. Ho mostrato come creare un proxy dinamico generico basato sul modello di progettazione decoratore che riguarda aspetti classi utilizzando eventi e un predicato per filtrare le funzioni desiderate.
Come potete vedere, la classe RealProxy è una classe flessibile e ti dà il controllo completo del codice, senza dipendenze esterne. Si noti tuttavia che RealProxy non è un sostituto di altri strumenti AOP, come PostSharp. PostSharp utilizza un metodo completamente diverso. Aggiungerà codice intermediate language (IL) in una fase di post-compilazione e non utilizzare la reflection, quindi dovrebbe avere prestazioni migliori rispetto a RealProxy. Dovrete anche fare più lavoro per implementare un aspetto con RealProxy rispetto con PostSharp. Con PostSharp, avete bisogno solo creare la classe di aspetto e aggiungere aggiungere un attributo alla classe (o il metodo) dove si desidera che l'aspetto, e questo è tutto.
D'altra parte, con RealProxy, avrete il pieno controllo del codice sorgente, senza dipendenze esterne, e si può estendere e personalizzarlo come volete. Ad esempio, se si desidera applicare un aspetto soltanto sui metodi che hanno l'attributo di Log, si potrebbe fare qualcosa di simile:
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
if (!methodInfo.CustomAttributes
.Any(a => a.AttributeType == typeof (LogAttribute)))
{
var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
...
Inoltre, la tecnica utilizzata dall'oggetto RealProxy (codice di intercettare e consentire al programma di sostituirla) è potente. Ad esempio, se si desidera creare un quadro beffardo, per la creazione di simulazioni generiche e stub per il test, è possibile utilizzare la classe RealProxy per intercettare tutte le chiamate e sostituirle con il proprio comportamento, ma questo è un argomento per un altro articolo!
Bruno Sonnino è un Microsoft MVP Most Valuable Professional () si trova in Brasile. Egli è un developer, consulente e autore, avendo scritto cinque libri di Delphi, pubblicati in portoghese da Pearson Education Brasile e molti articoli per il brasiliano e Stati Uniti riviste e siti Web.
Grazie ai seguenti esperti tecnici Microsoft Research per la revisione di questo articolo: James McCaffrey, Carlos Suarez e Johan Verwey
James McCaffrey lavora per Microsoft Research in Redmond, Washington Ha lavorato su diversi prodotti Microsoft, inclusi Internet Explorer e Bing. Può essere raggiunto a jammc@microsoft.com.
Carlos Garcia Jurado Suarez è un ingegnere del software di ricerca di Microsoft Research, dove ha lavorato nel Team di sviluppo avanzato e più recentemente il gruppo di Machine Learning. In precedenza, egli era uno sviluppatore in Visual Studio lavorando su strumenti quali Progettazione classi di modellazione.