Object triggers
Aunque es prematuro, todo sea por concretar un poco la idea:
// Freya
Editor = class
public
property CurrentPosition: Position;
event PositionChanged: EventHandler;
protected
method OnPositionChanged; virtual;
begin
var eh := PositionChanged;
if eh <> nil then
eh(Self, EventArgs.Empty);
end;
implementation
// Este es un trigger: se admite read, write y execute.
// Pueden combinarse varios recursos en un mismo trigger.
on CurrentPosition(write)
var
// Estas variables se convierten en campos
flag: Integer := 0;
begin
flag++;
// Esto sí es una verdadera variable local:
var initialPosition := currentPosition;
finally
flag--;
if flag = 0 then
if initialPosition <> currentPosition then
OnPositionChanged;
end;
end;
He intentado no meter palabras reservadas nuevas, al menos mientras no estén más claras las ideas. Lo que quiero averiguar es si con esta simple técnica se pueden lograr todas las cosas positivas de la AOP, sin provocar, a la misma vez, todos los problemas que ahora provoca. La clase, por supuesto, tendría mucho más código, y los triggers, por llamarlos de algún modo, siempre van en la implementación de la clase (no interesan al cliente de la clase).
Importante: los campos usados por un trigger son solamente visibles para el propio trigger. Por ello puede usarse un nombre como flag sin preocupaciones, porque de existir ya un miembro con este nombre, el campo se renombraría.
Y por supuesto, la historia puede complicarse todo lo que se quiera: ¿se podrían heredar triggers para aplicarse a nuevos recursos? Probablemente sí, en la medida en que el recurso protegido pueda ser modificado en una clase derivada. ¿Tiene sentido definir triggers independientemente del recurso que protegen, para enlazarlos luego con un recurso en una implementación? Supongo que dependerá de la complejidad del código que pueda meterse en un trigger.
Observe también que, en general, podría vigilarse más de un recurso con el mismo trigger. Imagine que, en vez de tener una variable (o una propiedad) que encapsula fila y columna, tenemos dos campos separados. En tal caso, el mismo trigger se "tejería" para los métodos que modificasen uno u otro campo. ¿Tiene sentido definir triggers para que se apliquen cuando un método acceda a uno y otro campo? No lo sé: tengo que encontrar un ejemplo con sentido. De esto, de encontrar más ejemplos, se trata ahora.
NOTAS
A quien haya seguido ejemplos anteriores en Freya, le extrañará probablemente la implementación en línea del método OnPositionChanged. No hay nada extraño: Freya sigue manteniendo el viejo formato, con las declaraciones en secciones public/private y secciones implementation for para las implementaciones. Pero ahora también es posible utilizar el estilo en línea, como en Eiffel (y Java y C#). Observe, de paso, que incluso en tal caso sigue existiendo una sección implementation, aunque en ella ya no se menciona la clase, por ser innecesario. En esta sección siguen residiendo los "detalles de implementación", que sería inapropiado situar en una sección "regular". Por ejemplo: constructores de clase (estáticos), delegaciones de implementación para tipos de interfaz, implementaciones explícitas de miembros de interfaces y destructores.
Una vez aclarado este punto, observe que CurrentPosition es una propiedad sin implementación explícita. En estos casos, el compilador declara un identificador oculto y genera automáticamente los métodos de acceso necesarios. Mejor aún: cuando se hace referencia a la propiedad dentro de la misma clase, ésta se sustituye por una referencia al campo. Y si la propiedad no es virtual, el campo oculto se declara internal, y la optimización se realiza también para los usos de la propiedad dentro del propio ensamblado, con independencia de la clase. Si alguna vez se cuestiona la necesidad de Freya, recuerde que la calidad del código generado es muy superior a la media de los compiladores para .NET, a pesar de las limitaciones impuestas por la plataforma :)
Otro pequeño detalle: observo cómo copio el valor del evento PositionChanged antes de comprobar su valor y ejecutarlo. Es común ver por ahí código que no utiliza una variable temporal, pero aunque teóricamente es correcto, podemos tener problema si alguna vez accedemos al mismo objeto desde dos hilos paralelos. La técnica mostrada es la "correcta", teniendo en cuenta esta posibilidad.