Si no fuera porque usas los helpers, genéricos e interfaces mucho más que yo... juraría que son mi clon... tu tampoco usas data-ware, usas patrones cuando puedes, eres analista y hasta que no tienes estudiado el tema no metes código...
No hay otra, o sos un clon o es que finalmente ha sucedido lo que más temía: ¡Una de mis personalidades ha logrado salir de la matrix y cobró vida propia!
Jaja lo que van a sufrir los que tengan que leer todas las locuras que ponemos. Es muy cierto que hay muchas cosas que compartimos, incluso algunos habitos muy raros
--
Siguiendo con el tema me esta gustando esto de la fluides. Creo que me voy a poner en Status = Superfluido
Por ejemplo hoy se me ocurrio (para seguir divirtiendome un rato) escribir otra interface fluent para rellenar TPopupMenus
La idea es que hay una muy sencilla interface IAction que tengo dando vueltas por ahi hace rato y tiene algunas propiedades (Caption, ShortCut, y Enabled creo que son las mas pertinentes, el resto son cosas de logica de negocios) y unos metodos BeforeExecute y Execute
Bueno, el resultado fue escribir cosas como esta:
procedure TForm1.CreateForm(Sender: TObject);
var
Action: IAction;
begin
Action := {* ... crear este objeto ... *}
OverflowMenu
.AddAction(Action)
.AddSeparator
.AddCustomAction
.WithShortCut('A', [ssCtrl])
.WithCaption('&Agregar usuario')
.OnExecute(
procedure
begin
// ...
end)
.OnBeforeExecute(
procedure
begin
// ...
end)
.Menu { vuelvo a obtener la referencia "original", porque dentro de la cadena de mensajes
al invocar a AddCustomAction, cambio el tipo de retorno a otra interface que es la
que tiene los metodos WithCaption, WithShortCut, OnExecute }
.AddSeparator
.AddCustomAction
.WithCaption('&Eliminar usuario') // puedo invertir el orden si quisiera
.WithShortCut('E', [ssCtrl])
.OnExecute(
procedure
begin
// ...
end);
En las primeras lineas se puede ver como se crea un objeto y se agrega en la coleccion, hasta ahi nada del otro mundo; lo unico que puede verse que cambia es que inmediatamente puedo invocar a AddSeparator
Mientras escribia el codigo se me ocurrio que podia ser util y ademas un nuevo desafio lograr lo que se ve en las lineas que sigen
La idea es poder crear acciones "on-the-fly", o "al vuelo", sin tener que andar implementando la clase. Al mismo tiempo tiene que ser compatible con la sintaxis fluida y la interface, es decir, necesito que implemente IAction con todos sus metodos y propiedades, y de alguna manera proveer un mecanismo en este armado "al vuelo" para poder asignar esos valores
El secreto esta, en como dice el comentario, cambiar el tipo de retorno
Paso a dejar la declaracion de las interfaces:
{$REGION 'IMenuItemAction'}
/// <summary> Extiende IAction con algunas caracteristicas para interfaz grafica </summary>
IMenuItemAction = interface(IAction)
['{7F7F5CFB-CA1E-4D2F-82E3-509791264ECB}']
function GetCaption: string;
function GetEnabled: Boolean;
procedure SetCaption(const Value: string);
procedure SetEnabled(const Value: Boolean);
/// <summary> El texto que se muestra en el menu </summary>
property Caption: string read GetCaption write SetCaption;
/// <summary>
/// Determina si la accion esta habilitada o deshabilitada
/// True: La accion esta habilitada y se puede ejecutar
/// False: La accion esta deshabilitada y no se puede ejecutar
/// </summary>
property Enabled: Boolean read GetEnabled write SetEnabled;
end;
{$ENDREGION}
ICustomMenuItemAction = interface;
{$REGION 'IOverflowMenu'}
/// <summary> Administra una coleccion de acciones que se muestran en un menu desplegable </summary>
IOverflowMenu = interface
['{7351FD4D-EEC8-4035-BD6B-C84148A61BE0}']
function GetActions: Spring.Collections.IEnumerable<IMenuItemAction>;
function GetIsEmpty: Boolean;
/// <summary> Agrega una accion al menu en donde su metodo Execute se implementa con un metodo anonimo </summary>
function AddCustomAction: ICustomMenuItemAction;
/// <summary> Agrega una accion al menu </summary>
function AddAction(const Action: IMenuItemAction): IOverflowMenu;
/// <summary> Agrega una coleccion de acciones al menu </summary>
function AddActions(const Actions: TArray<IMenuItemAction>): IOverflowMenu; overload;
/// <summary> Agrega una coleccion de acciones al menu </summary>
function AddActions(const Actions: Spring.Collections.IEnumerable<IMenuItemAction>): IOverflowMenu; overload;
/// <summary> Agrega un separador en la posicion indicada por Index; si Index es < 0 lo agrega al final </summary>
function AddSeparator(Index: Integer = -1): IOverflowMenu;
/// <summary> Elimina la Accion del menu </summary>
function RemoveAction(const Action: IMenuItemAction): IOverflowMenu;
/// <summary> Elimina todas las acciones del menu </summary>
function ClearActions: IOverflowMenu;
/// <summary> Muestra/despliega el menu </summary>
procedure DisplayMenu;
/// <summary> Oculta/cierra el menu </summary>
procedure HideMenu;
/// <summary> Coleccion de acciones que contiene este menu </summary>
property Actions: Spring.Collections.IEnumerable<IMenuItemAction> read GetActions;
/// <summary> Devuelve True si el OverflowMenu esta vacio; no tiene en cuenta los separadores </summary>
property IsEmpty: Boolean read GetIsEmpty;
end;
{$ENDREGION}
{$REGION 'ICustomMenuItemAction'}
/// <summary>
/// Usada para crear acciones sin necesidad de implementar IMenuItemAction
/// Estas acciones se crean dinamicamente usando fluent interface
/// El metodo Execute se define mediante un metodo anonimo
/// Crear este tipo de acciones usando IOverflowMenu.AddCustomAction
/// </summary>
ICustomMenuItemAction = interface(IMenuItemAction)
['{C7882155-D3AB-44D9-83BD-2EECE9BC7C19}']
function GetMenu: IOverflowMenu;
/// <summary> Setea la propiedad ShortCut de la accion </summary>
function WithShortCut(const Key: Char; const Shift: TShiftState): ICustomMenuItemAction; overload;
/// <summary> Setea la propiedad ShortCut de la accion </summary>
function WithShortCut(const Key: Word; const Shift: TShiftState): ICustomMenuItemAction; overload;
/// <summary> Setea la propiedad ShortCut de la accion </summary>
function WithShortCut(const Value: TShortCut): ICustomMenuItemAction; overload;
/// <summary> Setea la propiedad Caption de la accion </summary>
function WithCaption(const Value: string): ICustomMenuItemAction;
/// <summary> Setea el metodo anonimo que implementa el metodo Execute de la accion </summary>
function OnExecute(const Value: TProc): ICustomMenuItemAction;
/// <summary> Setea el metodo anonimo que implementa el metodo BeforeExecute de la accion </summary>
function OnBeforeExecute(const Value: TProc): ICustomMenuItemAction;
/// <summary> Referencia al Menu que contiene la accion. Permite volver a la cadena de metodos fluent </summary>
[Weak] property Menu: IOverflowMenu read GetMenu;
end;
{$ENDREGION}
Como se puede ver, la primera interface define el protocolo de las acciones que puede "ejecutar" el menu
Luego IOverflowMenu es quien administra todo eso y se encarga de presentar todo al usuario en algun componente. Como me gusta mantenerme en lo abstracto decidi no "atar" a ningun componente en particular, y dejar que la implementacion se encarge del trabajo sucio.
Como en el primer post, el secreto es el mismo, los metodos que normalmente serian procedimientos sin valor de retorno, ahora son funciones que siempre devuelven Self
Luego hay una tercera interface, ICustomMenuItemAction, que es la que hace posible que se puedan crear las acciones como en el codigo de ejemplo que mostre arriba
Como se ve, extiende a la interface IMenuItemAction que es la que "entiende" el menu; sino, el codigo ni compilaria
La interface de mas arriba en su jerarquia, estan basadas en propiedades con sus getter y setter como cualquier cosa "normal" en Delphi. Es decir tenemos propiedades como Caption y su getter GetCaption. El retorno de GetCaption es un string, y yo necesito seguir enviandole mensajes al mismo objeto; y las propiedades como SetCaption, son procedimientos, no admiten valor de retorno, por lo que tampoco sirven
Entonces el truco es ese, el metodo AddCustomAction en lugar de retornar el menu, crea y devuelve un objeto que implementa ICustomMenuItemAction, y a ese si que puedo mandarle otros mensajes. Ahi es donde estan definidos WithCaption, WithShortCut, etc. Pareceria que estoy duplicado codigo,, parece que si porque esos metodos lo unico que hacen es asignar el valor usando las propiedades heredadas, pero ojo, retornan Self; y entonces puedo seguir con la cadena
La propiedad Menu es la que me permite volver a la referencia original para seguir agregando cosas al menu. Es como decir, listo termine con esta accion custom, ahora quiero seguir agregando mas al menu
Y finalmente para que la accion haga algo, necesito proveerla de funcionalidad.. pero no estoy escribiendo una clase para dicha accion, como hago para que cuando se invoque su metodo Execute "pase algo"? Y la respuesta fueron los metodos anonimos
Los metodos anonimos no son mas que un puntero a una funcion/procedimiento. Se pueden usar como cualquier variable, es decir, se pueden almacenar en una variable de instancia por ejemplo; y se puede invocar a su codigo cuando se de la gana
Basicamente, en AddCustomAction, se crea una clase que implementa todas las interfaces necesarias, y de hecho los metodos Execute y BeforeExecute estan implementados asi:
TCustomMenuItemAction = class(TInterfacedObject, IAction, IMenuItemAction, ICustomMenuItemAction)
strict private
[Weak] FMenu: IOverflowMenu; // para devolver en el metodo GetMenu
FExecuteProc: TProc; // TProc esta definido en SysUtils asi: reference to procedure
FBeforeExecuteProc: TProc; // osea que se puede almacenar en el cualquier procedimiento
procedure Execute;
procedure BeforeExecute;
public
constructor Create(AMenu: IOverflowMenu);
property ExecuteProc: TProc read FExecuteProc write FExecuteProc;
property BeforeExecuteProc: TProc read FBeforeExecuteProc write FBeforeExecuteProc;
end;
function TCustomMenuItemAction .WithCaption(const Value: string): ICustomMenuItemAction;
begin
SetCaption(Value);
Result := Self;
end;
procedure TCustomMenuItemAction.BeforeExecute;
begin
if Assigned(BeforeExecuteProc) then
BeforeExecuteProc();
end;
procedure TCustomMenuItemAction.Execute;
begin
BeforeExecute;
if Assigned(ExecuteProc) then // el check por nil es necesario
ExecuteProc(); // -> los parentesis son necesarios para decir que estamos "invocando"
end;
Como se ve no es tan complicado
En fin puedo concluir que la programacion fluida puede resultar muy util para algunos casos; aumenta enormemente la legibilidad del codigo y evita repetir partes del mismo; por lo pronto, no se repite todo el tiempo el objeto al que le enviamos el mensaje
Tambien creo que brilla mas si se usa para trabajar con ciertas estructuras de datos; he visto fragmentos de codigo que trabajan con nodos xml, arboles, grafos. En esos casos su sintaxis minimalista, limpia y clara, con ese sangrado para separar niveles, aporta bastante a la legibilidad
Pero como todas las cosas tiene sus contras. La peor es que en realidad por mas que escribamos 100 lineas fluidas, para el programa es una sola. Me explico mejor, si yo pongo un punto de ruptura en la primera linea y luego doy F8, el codigo pasa a la linea siguiente al final de la parte fluida; es decir, a nivel de depuracion, es bastante engorroso. No se pueden poner puntos de ruptura en todo ese "treeview" de invocaciones encadenadas de metodos
La segunda contra esta relacionada con la performance. En cada invocacion a cada metodo estamos activando el conteo de referencias de interfaces, lo cual no tiene sentido y como para colmo la implementacion de TInterfacedObject (que supongoque usamos todos para implementar interfaces ) es thread-safe, usa TInterlocked para incrementar referencias y eso obviamente es mas lento. Este punto es mas subjetivo ya que solo tendria sentido en casos como los mencionados procesar grandes archivos xml u otra estructura de datos inmensa.
A ver si alguien se anima a inventar alguna cosa rara y superfluida!
Saludos
Editado por Agustin Ortu, 31 agosto 2016 - 09:17 .