Acabo de ver esta charla de Greg Young advirtiendo a la gente a KISS: Keep It Simple Stupid.
Una de las cosas que él sugiere es que para hacer la programación orientada a aspectos, uno qué no se necesita un marco .
Comienza haciendo una fuerte restricción: que todos los métodos toman uno, y solo un parámetro, (aunque lo relaja un poco más tarde usando una aplicación parcial ).
El ejemplo que da es definir una interfaz:
public interface IConsumes<T>
{
void Consume(T message);
}
Si queremos emitir un comando:
public class Command
{
public string SomeInformation;
public int ID;
public override string ToString()
{
return ID + " : " + SomeInformation + Environment.NewLine;
}
}
El comando se implementa como:
public class CommandService : IConsumes<Command>
{
private IConsumes<Command> _next;
public CommandService(IConsumes<Command> cmd = null)
{
_next = cmd;
}
public void Consume(Command message)
{
Console.WriteLine("Command complete!");
if (_next != null)
_next.Consume(message);
}
}
Para iniciar sesión en la consola, uno solo implementa:
public class Logger<T> : IConsumes<T>
{
private readonly IConsumes<T> _next;
public Logger(IConsumes<T> next)
{
_next = next;
}
public void Consume(T message)
{
Log(message);
if (_next != null)
_next.Consume(message);
}
private void Log(T message)
{
Console.WriteLine(message);
}
}
Entonces, el registro previo al comando, el servicio de comando y el registro posterior al comando son solo:
var log1 = new Logger<Command>(null);
var svr = new CommandService(log);
var startOfChain = new Logger<Command>(svr);
y el comando es ejecutado por:
var cmd = new Command();
startOfChain.Consume(cmd);
Para hacer esto en, por ejemplo, PostSharp , uno anotaría deCommandService
esta manera:
public class CommandService : IConsumes<Command>
{
[Trace]
public void Consume(Command message)
{
Console.WriteLine("Command complete!");
}
}
Y luego tiene que implementar el registro en una clase de atributo algo así como:
[Serializable]
public class TraceAttribute : OnMethodBoundaryAspect
{
public override void OnEntry( MethodExecutionArgs args )
{
Console.WriteLine(args.Method.Name + " : Entered!" );
}
public override void OnSuccess( MethodExecutionArgs args )
{
Console.WriteLine(args.Method.Name + " : Exited!" );
}
public override void OnException( MethodExecutionArgs args )
{
Console.WriteLine(args.Method.Name + " : EX : " + args.Exception.Message );
}
}
El argumento que usa Greg es que la conexión del atributo a la implementación del atributo es "demasiada magia" para poder explicar lo que le está sucediendo a un desarrollador junior. El ejemplo inicial es todo "solo código" y se explica fácilmente.
Entonces, después de esa acumulación bastante larga, la pregunta es: ¿cuándo cambias el enfoque no marco de Greg para usar algo como PostSharp para AOP?
fuente
IConsumes
piezas diferentes . En lugar de tener que usar XML externo o alguna interfaz fluida, otra cosa más que aprender. Se podría argumentar que esta metodología es "otra cosa para aprender" también.Respuestas:
¿Está tratando de escribir un marco AOP "directo a TDWTF"? En serio, todavía no tengo idea de cuál era su punto. Tan pronto como diga "Todos los métodos deben tomar exactamente un parámetro", entonces ha fallado, ¿no? En esa etapa, usted dice: OK, esto impone algunas limitaciones muy artificiales en mi capacidad para escribir software, dejemos esto ahora antes, tres meses después tenemos una base de código de pesadilla completa para trabajar.
¿Y sabes qué? Puede escribir un marco de registro basado en IL basado en atributos simple con bastante facilidad con Mono.Cecil . (probarlo es un poco más complicado, pero ...)
Ah e IMO, si no estás usando atributos, no es AOP. El objetivo de hacer el código de registro de entrada / salida del método en la etapa de postprocesador es para que no se enrede con los archivos de código y no tenga que pensar en ello mientras refactoriza su código; Ese es su poder.
Todo lo que Greg ha demostrado es mantener el estúpido paradigma estúpido.
fuente
let concat (x : string) y = x + y;; concat "Hello, " "World!";;
Parece que se necesitan dos argumentos, ¿qué me estoy perdiendo?concat "Hello, "
eso estás creando una función que toma soloy
y se hax
predefinido como un enlace local para ser "Hola". Si se pudiera ver esta función intermedia, se vería algo asílet concat_x y = "Hello, " + y
. Y luego de eso, estás llamandoconcat_x "World!"
. La sintaxis hace que sea menos obvio, pero esto le permite "hornear" nuevas funciones - por ejemplo,let printstrln = print "%s\n" ;; printstrln "woof"
. Además, incluso si haces algo comolet f(x,y) = x + y
eso, en realidad es solo un argumento de tupla .Dios mío, ese tipo es intolerablemente abrasivo. Desearía haber leído el código en tu pregunta en lugar de haber visto esa charla.
No creo que usaría este enfoque si fuera solo por el uso de AOP. Greg dice que es bueno para situaciones simples. Esto es lo que haría en una situación simple:
Sí, lo hice, me deshice de AOP por completo. ¿Por qué? Porque no necesitas AOP en situaciones simples .
Desde el punto de vista de la programación funcional, permitir solo un parámetro por función realmente no me asusta. Sin embargo, este realmente no es un diseño que funcione bien con C #, y ir contra la corriente de tu lenguaje no BESA nada.
Solo usaría este enfoque si fuera necesario hacer un modelo de comando para comenzar, por ejemplo, si necesitaba una pila de deshacer o si estaba trabajando con los comandos de WPF .
De lo contrario, solo usaría un marco o alguna reflexión. PostSharp incluso funciona en Silverlight y Compact Framework, por lo que lo que él llama "magia" realmente no es mágico en absoluto .
Tampoco estoy de acuerdo con evitar los frameworks por el simple hecho de poder explicar las cosas a los juniors. No les está haciendo ningún bien. Si Greg trata a sus juniors de la forma en que sugiere que sean tratados, como idiotas de cráneos gruesos, entonces sospecho que sus desarrolladores senior tampoco son muy buenos, ya que probablemente no se les haya dado la oportunidad de aprender nada durante su años junior
fuente
Hice un estudio independiente en la universidad sobre AOP. De hecho, escribí un artículo sobre un enfoque para el modelo AOP con un complemento Eclipse. Eso en realidad es algo irrelevante, supongo. Los puntos clave son 1) era joven e inexperto y 2) estaba trabajando con AspectJ. Les puedo decir que la "magia" de la mayoría de los marcos de AOP no es tan complicada. De hecho, trabajé en un proyecto al mismo tiempo que intentaba hacer el enfoque de parámetro único usando una tabla hash. En mi opinión, el enfoque de parámetro único realmente es un marco y es invasivo. Incluso en esta publicación, pasé más tiempo tratando de comprender el enfoque de parámetro único que revisando el enfoque declarativo. Agregaré una advertencia de que no he visto la película, por lo que la "magia" de este enfoque puede estar en el uso de aplicaciones parciales.
Creo que Greg respondió tu pregunta. Debes cambiar a este enfoque cuando creas que estás en una situación en la que pasas una cantidad excesiva de tiempo explicando los marcos de AOP a tus desarrolladores junior. En mi opinión, si estás en este barco, probablemente estés contratando a los desarrolladores junior incorrectos. No creo que AOP requiera un enfoque declarativo, pero para mí, es mucho más claro y no invasivo desde una perspectiva de diseño.
fuente
IConsume<T>
ejemplo demasiado complicado para lo que se está logrando.A menos que me falte algo, el código que ha mostrado es el patrón de diseño de 'cadena de responsabilidad' que es excelente si necesita conectar una serie de acciones en un objeto (como los comandos que pasan por una serie de controladores de comandos) en tiempo de ejecución
El AOP que utiliza PostSharp es bueno si sabe en el momento de la compilación qué comportamiento desea agregar. El tejido de código de PostSharp significa que no hay sobrecarga de tiempo de ejecución y mantiene el código muy limpio (especialmente cuando comienzas a usar cosas como aspectos de multidifusión). No creo que el uso básico de PostSharp sea particularmente complejo de explicar. La desventaja de PostSharp es que aumenta significativamente los tiempos de compilación.
Utilizo ambas técnicas en el código de producción y, aunque hay cierta superposición en el lugar donde se pueden aplicar, creo que en su mayor parte realmente apuntaron a diferentes escenarios.
fuente
Con respecto a su alternativa: estado allí, hecho eso. Nada se compara con la legibilidad de un atributo de una línea.
Dé una breve conferencia a los nuevos chicos explicándoles cómo funcionan las cosas en AOP.
fuente
Lo que Greg describe es absolutamente razonable. Y también hay belleza en ello. El concepto es aplicable en un paradigma diferente al de la orientación pura a objetos. Es más un enfoque de procedimiento o un enfoque de diseño orientado al flujo. Por lo tanto, si está trabajando con código heredado, será bastante difícil aplicar este concepto porque podría ser necesaria una gran refactorización.
Trataré de dar otro ejemplo. Tal vez no sea perfecto, pero espero que aclare el punto.
Entonces tenemos un servicio de producto que usa un repositorio (en este caso usaremos un código auxiliar). El servicio obtendrá una lista de productos.
Por supuesto, también podría pasar una interfaz al servicio.
A continuación, queremos mostrar una lista de productos en una vista. Por eso necesitamos una interfaz
y un comando que contiene la lista de productos
y la vista
Ahora necesitamos un código que ejecute todo esto. Esto lo haremos en una clase llamada Aplicación. El método Run () es el método de integración que no contiene o al menos muy poca lógica de negocios. Las dependencias se inyectan en el constructor como métodos.
Finalmente componimos la aplicación en el método principal.
Ahora lo bueno es que podemos agregar aspectos como el registro o el manejo de excepciones sin tocar el código existente y sin un marco o anotaciones. Para el manejo de excepciones, por ejemplo, simplemente agregamos una nueva clase:
Y luego lo conectamos durante la composición en el punto de entrada de la aplicación. ni siquiera tenemos que tocar el código en la clase de Aplicación. Solo reemplazamos una línea:
Para resumir: cuando tenemos un diseño orientado al flujo, podemos agregar aspectos agregando la funcionalidad dentro de una nueva clase. Luego tenemos que cambiar una línea en el método de composición y listo.
Entonces, creo que una respuesta a su pregunta es que no puede cambiar fácilmente de un enfoque a otro, sino que debe decidir qué tipo de enfoque arquitectónico adoptará en su proyecto.
editar: En realidad, me acabo de dar cuenta de que el patrón de aplicación parcial utilizado con el servicio del producto hace las cosas un poco más complicadas. Necesitamos incluir otra clase alrededor del método de servicio del producto para poder agregar aspectos aquí también. Podría ser algo como esto:
La composición debe cambiarse así:
fuente