Ir al contenido


Foto

Cómo implementar una Clase que se autolibere

Delphi interface

  • Por favor identifícate para responder
18 respuestas en este tema

#1 genriquez

genriquez

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 539 mensajes
  • LocationCali, Colombia

Escrito 30 junio 2016 - 07:53

Hola amigos.

 

Esta pregunta es algo básico, pero la estoy tratando de implementar, y no me funciona, al perder las referencias no lo libera.

 

 

Tengo una clase, cuyas instancias deben estar "vivas" mientras alguien las esté usando, en el momento en que pierda toda referencia deben liberarse automáticamente,  se que esto se implementa con una interface en la definición de la clase, pero al tratar de hacerlo, no se están liberando.

 

Estoy utilizando Delphi, FireMonkey DX10.

 

Si alguien tiene un ejemplo básico, que esté comprobado que funcione, me lo podrían regalar.

 

Gracias.


  • 0

#2 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 30 junio 2016 - 12:02

No tengo un ejemplo preparado a mano, y desconozco si es que en FireMonkey las cosas son al menos ligeramente diferentes en lo que es la VCL. En teoría, como bien dices, una forma de hacerlo es justamente emplear el poder que ofrece las interfaces.

 

Básicamente la idea es:

1. Definir la/s interface/s base/s que tenga/n la descripción de los métodos y/o propiedades de interés.

2. Definir la/s clase/s y que implementará/n la/s interface/s. No olvidar que esta clase debe heredar de InterfacedObject y de las interfaces. En ese orden. Es decir algo como:


delphi
  1. Type
  2. TMiClase = class(TInterfacedObject, TMiInterface1, ... TMiInterfaceN);
  3. private
  4. // etc
  5. protected
  6. // etc
  7. public
  8. // etc
  9. end;

3. Declarar las variables que sean necesarias

4. Instanciar

 

Recuerda que al usar interfaces no se debe en ningún momento hacer un Free o FreeAndNil, no hay que emplear liberación alguna. Es automático. Justamente al definir interfaces y al emplear que hereden de TInterfacedObject se consigue manejar el conteo de referencias. O dicho más en cristiano: que sea el propio sistema el que lleve la cuentas y cuando esta llega a 0 se liberan los recursos.

 

La otra posibilidad es emplear un Singleton. La forma más sencilla de hacerlo: definir una única variable de cada clase interesada y la que se usará globalmente. El tiempo de vida de estas variables será hasta que se finalice el programa... que se puede conseguir fácilmente con Initialization y Finalization.

La forma más rebuscada de tener un Singleton es sobreescribir el método NewInstance y hacer el trabajo "duro" nosotros. Acá tienes un buen artículo al respecto.

 

Si nos muestra algo de tu código quizá podamos ver donde está el problema.

 

Saludos,


  • 1

#3 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 30 junio 2016 - 04:08

La implementacion de las interfaces en Delphi podes considerarlas como parte de la RTL; por lo tanto, es independiente de la plataforma/bitness/framework.

 

Podrias poner un ejemplo en el que los objetos no se liberen? Tenes referencias circulares? 

 

Lo que comento marcelo es correcto

 

Lo que tenes que tener muy muy en cuenta es que no debes mezclar referencias a interfaces con referencias a objetos

 

Ejemplo:


delphi
  1. type
  2. TPersona = (TInterfacedObject, INamed)
  3. private
  4. FName: string;
  5. function GetName: string; // INamed
  6. procedure SetName(const Value: string); // INamed
  7. protected
  8. property Name: string read GetName write SetName; // INamed
  9. end;
  10.  
  11.  
  12. { ... }
  13.  
  14. procedure ...;
  15. var
  16. aPersona: TPersona; // peligroso, siendo que TPersona es un "TInterfacedObject";
  17. aNamed: INamed; // seguro;
  18. begin
  19. aPersona := TPersona.Create; // peligroso por mezclar referencias entre objetos e interfaces
  20. // una buena forma de "cubrirse" contra esto es declarar todos los metodos
  21. // que implementa la interface y sus propiedades como protegidos/privados
  22. // de esta forma, la unica forma de usar la clase TPersona es mediante interfaces
  23.  
  24. aNamed := TPersona.Create; // todo ok!
  25. end;

Si tenes referencias circulares, ahi la cosa cambia. Para las plataformas "ARC-enabled", hasta Delphi 10 Seattle inclusive, eso lo solucionas con un atributo, el atributo [Weak]. 

 

Te dejo algunos enlaces con informacion y algunos "workaround" para versiones anteriores de Delphi:

 

http://docwiki.embar...Weak_References

 

http://stackoverflow...lanation-needed

 

https://github.com/V...akReference.pas

 

http://delphisorcery...references.html

 

http://blog.marcocan...references.html


  • 0

#4 genriquez

genriquez

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 539 mensajes
  • LocationCali, Colombia

Escrito 30 junio 2016 - 05:27

Hola, concretamente tengo el siguiente código,  yo esperaría que al asingar nil o al salir del alcance de la variable, se diparara el Destroy, para eliminar los objetos creados en el créate.
 
Al agregar un breakpoint nunca ejecuta el Destroy.
 
Saludos.

delphi
  1. unit Unit11;
  2.  
  3. interface
  4.  
  5. uses   System.SysUtils, System.Types, System.UITypes,
  6. System.Classes, System.Variants,   FMX.Types, FMX.Controls,
  7. FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation,
  8. FMX.StdCtrls;
  9.  
  10. type
  11.  
  12.    ILeapFrame = Interface
  13.      
  14. ['{9834F1E7-1F2D-4674-8E09-A1E8D2B72EAD}']
  15.    End;
  16.  
  17.    TLeapFrame = Class(TInterfacedObject, ILeapFrame)
  18.    Private
  19.       FId: String;
  20.       FLista : TList;
  21.       procedure SetId(const Value: String);
  22.    Protected
  23.    Public
  24.       Constructor Create;
  25.       Destructor Destroy; Override;
  26.       Property Id: String read FId write
  27. SetId;
  28.    End;
  29.  
  30.    TForm11 = class(TForm)
  31.       Button1: TButton;
  32.       procedure Button1Click(Sender:
  33. TObject);
  34.    private
  35.       { Private declarations }
  36.    public
  37.       { Public declarations }
  38.    end;
  39.  
  40. var
  41.    Form11: TForm11;
  42.  
  43. implementation
  44.  
  45. {$R *.fmx}
  46.  
  47. procedure TForm11.Button1Click(Sender: TObject);
  48. Var
  49.    Frame : TLeapFrame;
  50. begin
  51.    Frame := TLeapFrame.Create;
  52.    Frame.Id := '10';
  53.    Frame := Nil;
  54. end;
  55.  
  56. { TLeapFrame }
  57.  
  58. constructor TLeapFrame.Create;
  59. begin
  60.   inherited;
  61.   FLista := TList.Create;
  62. end;
  63.  
  64. destructor TLeapFrame.Destroy;
  65. begin
  66.   FLista.Free;
  67.   inherited;
  68. end;
  69.  
  70. procedure TLeapFrame.SetId(const Value: String);
  71. begin
  72.    FId := Value;
  73. end;
  74.  
  75. end.


  • 0

#5 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 30 junio 2016 - 07:15


delphi
  1. procedure TForm11.Button1Click(Sender: TObject);
  2. Var
  3. Frame : ILeapFrame; // cambiado el tipo de la variable a la interface
  4. begin
  5. Frame := TLeapFrame.Create;
  6. Frame.Id := '10';
  7. Frame := nil;
  8. end;

Prueba de este modo (la linea que asigna nil esta de mas, ya que al terminar ese metodo la interface pierde su ultima referencia y se destruye)


Editado por Agustin Ortu, 30 junio 2016 - 07:16 .

  • 0

#6 genriquez

genriquez

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 539 mensajes
  • LocationCali, Colombia

Escrito 01 julio 2016 - 06:17

Efectivamente ese era el problema, Muchas gracias a todos por la ayuda,  la verdad llevaba algunos días con esa inquietud.


  • 0

#7 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 01 julio 2016 - 06:43

Yo también tuve hace un tiempo un problemita con unas clases que implementaban interfaces. Lo había solucionado justamente al eliminar algunos Free que encontré en mi código. Esto me valió una nota mental. ;)

 

Lo que me llama la atención son las palabras de Agustín. ¿Porqué no es seguro tener una variable de la clase? O mejor dicho de otra forma: ¿Cuándo es seguro emplear una variable de la clase y cuando la de una interfaz?

Si el objeto va a ser único, o singleton para el caso, da lo mismo ¿O no?

 

Lo que no entiendo, aunque admito que no me he puesto a leer con demasiado interés (ya tenía quemadas mis neuronas anoche) es ¿que es o que hace ese weak? No termino de entenderlo.

 

Saludos,


  • 0

#8 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 01 julio 2016 - 11:22

Buenas Marce

El problema con TInterfacedObject es que el compilador solito te agrega las llamadas a AddRef y Release. Quiza no esté tan mal como en mi ejemplo usar la clase y no la interface, pero donde si es para lío es después referenciar el mismo objeto como interface.

Puede parecer un poco contradictorio porque por ejemplo dentro de la VCL y fmx las interfaces se usan un montón pero cuando programamos usamos siempre clases.. la explicación es que no son derivados de TInterfacedObject sino de TComponent que realmente deshabilita el conteo de referencias para pasar al modelo de owner

Por otro lado una referencia weak es cuando dos interfaces se referencian entre si. Cuando eso pasa el contador de referencias de ambos siempre 1 y son objetos que nunca se liberan. Imagínate el caso de un botón dentro de un form: el form conoce al botón mediante la colección de controles y de componentes que contiene; y el botón conoce al form tanto por owner como por parent. Ambos objetos tienen permanentemente el contador de referencias a 2 como mínimo y nunca se liberan (fuga de memoria). Supongamos que hay una variable global que referencia al form, o la que referencia el objeto TApplication, entonces form tendría su contador de referencias en 3 (dicha variable, botón.owner, botón.parent)

Una referencia weak es decirle al compilador que no lleve conteo de referencias para la variable en cuestión. En el ejemplo anterior, si en el botón ponemos las referencias al parent y owner como weak (es decir, las variables de instancia), el form queda con contador de referencias igual a 1. La única referencia que se cuenta es la variable global. Cuando se destruye el objeto TApplication, el contador de referencias de form pasa a 0. En ese momento se destruye el form. Cuando eso ocurre, se destruyen las colecciones que contienen los componentes del form (botón pasa de 2 a una referencia) y luego la colección de controles del form (botón pasa a 0 y se destruye).

Paréntesis aparte, para el caso anterior use el ejemplo de form y un botón porque es más fácil de ver para el Delphiniano. Pero notar que cuando se trata de TComponent (TForm es una subclase) el modelo de memoria es totalmente distinto. Simplemente use un escenario que cualquiera puede entender como se relacionan los objetos y los problemas de tener referencias circulares cuando estamos bajo un modelo de memoria ARC
  • 1

#9 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 01 julio 2016 - 12:21

Para que quede bien claro, lo que es seguro problematico es esto:


delphi
  1. procedure Foo;
  2. var
  3. unObjeto: TImplementacionDeAlgunaInterface;
  4. begin
  5. unObjeto:= TImplementacionDeAlgunaInterface.Create; // aca si bien creamos un objeto de cierta clase, nuestra referencia es una interface
  6. // supongamos que hereda directa o indirectamente de TInterfacedObject
  7.  
  8. HacerAlgoConLaInterface(unObjeto as IAlguaInterface); // enviamos como parametro nuestro objeto, pero lo casteamos a una interface, porque es lo que el metodo espera
  9. end;
  10.  
  11. procedure HacerAlgoConLaInterface(const unaInterface: IAlguaInterface);
  12. begin
  13.  
  14. end;

En ese caso estamos mezclando objetos con interfaces. Algunos links:

 

http://delphisorcery...ng-objects.html-- Este no esta directamente relacionado, es un "bug de Delphi". Pero hay un pequeño parrafo dedicado a este tema:

 

 

 

Just another reason why Delphi sometimes is a pain in the ass if you are using object references and interfaces. Some may say: "Just use interfaces!"

 

"... otra razon por la cual a veces Delphi es un dolor de cabeza si estas usando punteros a objetos e interfaces. Algunos diran: 'Solamente usa interfaces'..."

 

http://stackoverflow...-for-interfaces

http://stackoverflow...se-to-be-called

http://www.delphigro.../10/383013.html


  • 0

#10 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 01 julio 2016 - 12:46

Por otro lado, con respecto al Singleton, es un patron que nunca me gusto para nada. Pero peor aun la forma que esta implementada como en el articulo que esta enlazado mas arriba

 

Yo encuentro 2 problemas adicionales a esa implementacion del Singleton. Primero, que esa clase no se puede reusar. Es decir, si tengo un objeto que es un Singleton, tengo que implementar un TSingletonA. Luego, resulta que necesito otro objeto (de otro tipo, que hace otras cosas y no tiene nada que ver con el anterior) y no puedo reusar TSingletonA; tengo que copiar la implementacion y crear un TSingletonB:

 

 

  • Don't descend from a singleton class. The reason for that is that there is only one instance and reference count variable. If you derive two classes from TSingleton, only one object will be created. The other class will reuse that object, which will be an instance of a different class. Don't go down that road!

 

Segundo, estas obligando a la clase a solo crear un solo objeto, nunca mas. Se que parece tonto porque eso es lo que te pide el Singleton: asegurarse de que haya una unica instancia y proveer un punto global de acceso a ella.

 

Ahora, resulta que en otra aplicacion, queres reusar esa clase (no el singleton, sino la funcionalidad que provee). En esa aplicacion, resulta que no es un Singleton, ya que puede (debe) haber mas de una instancia. La clase no te sirve y tenes que modificarla para que no implemente el Singleton. Te quedas con dos versiones de la misma funcionalidad, en la que una es un Singleton y otra no. Si agregas caracteristicas o reparas un bug en una aplicacion, tenes que copiar el mismo codigo a la otra

 

Yo siempre odie esa forma de implementar Singleton. A mi me parece mucho mas sano tomarlo de esta manera: diseño la clase olvidandome del Singleton. Luego resulta que en una aplicacion, necesito un Singleton (una unica instancia). Perfecto, entonces creo una

 

Por que insisto con esto: El singleton que redefine el metodo NewInstance es una clase que automaticamente te acopla al estado. Es una clase dificil de testear porque al no poder "reiniciarla" (no, el metodo "reset" no es una solucion) los test que usen el Singleton no podran ser nunca independientes; tambien quiere decir que el resultado de los test puede estar dado segun su orden de ejecucion (por depender del "estado anterior" del singleton).

 

En resumen: Dificil de testear, viola SOLID: dos responsabilidades (una clase que se hace cargo de una funcionalidad + controlar el tiempo de vida), no puedo reusar la implementacion de Singleton, es una variable global, acoplamiento contra su estado

 

La variante que utilizo yo es, como ya dije, diseño la clase "normal" y luego simplemente creo una. Luego, la voy pasando como parametro a los objetos que la necesiten. Si es un objeto que utilizo desde muchas partes en la aplicacion, suelo crear un pequeño contenedor reusable.

 

No se como sera en otros lenguajes, pero en Delphi lamentablemente no existen variables de clase "por tipo". Es decir, esto resulta en una unica variable de instancia:


delphi
  1. TSingleton<T> = class abstract
  2. private
  3. class var FInstance: T;
  4. protected
  5. class function GetInstance: T; virtual;
  6. public
  7. class property Instance: T read GetInstance;
  8. end;

Es decir, si luego quiero tener un TSingleton<TSucursal> y a su vez un TSingleton<TEmpresa>, tendria una unica variable de clase FInstance: La de la clase generica y abstracta TSingleton<T>, y no una por cada "tipo"

 

Para solucionarlo, empleo un diccionario de pares "TClass, T", en donde para cada clase, tengo una instancia (el singleton). Si no existe en mi diccionario, debo crearlo. De este modo, puedo tener una clase comun sobre la cual heredar e implementar el patron una sola vez

 

Y ahora, mas en tema, con respecto a usar una interface en lugar de clase para implementar el Singleton, no hay ningun problema. Simplemente que no es necesario liberarlo explicitamente, eso se hace de manera automatica


  • 0

#11 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 01 julio 2016 - 01:08

Justamente la ventaja de heredar de TInterfacedObject es que esta ya implementa el conteo de referencias y no tenemos que hacer el trabajo nosotros.

Es posible no heredar de esta clase e implementar una interface, pero de hacerlo por esta vía tendremos que hacer ese trabajo sucio por nuestra cuenta.

 

Mi duda iba más a cuando la variable debe ser una clase y cuando una interface. Como dices, la mezcla no es buena. No me quedaba (ba... me queda aún un poco de duda) la idea de cuando usar una por otra, que tipos de problemas podría tener, etc en tu ejemplo inicial.

Lo de weak ahora me queda claro.

 

Yo comparto tu sentir sobre el patrón Singleton. La alternativa que se ofrece en el artículo es quizá la última a considerar, justamente por lo que comentas. Te permita una y solo una instancia. Ya sea de dicha clase o de alguna que la que se herede de ésta.

Como patrón el Singleton lo que dice es que debe dar respuesta a que formalmente exista una única instancia. Y ésta por lo general debe ser de alcance global. Más, no obliga ni condiciona a que se implemente de alguna forma concreta. Hay varias formas de llegar a la propuesta del Singleton, y la más fácil es simplemente tener una única variable creada e invocarla y/o pasarle como parámetro cuando sea necesario. Esta variable puede estar en una unidad fuera de la propia declaración (algo como una unidad global) o en la propieda unidad de la clase y aprovechar la sección Initialization y Finalization.

 

Saludos,


  • 1

#12 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 01 julio 2016 - 01:38

Asi es como implemento yo el Singleton: Fijate que fue muy muy sencillo extraer su implementacion, reusarla para cualquier clase que yo quiera, sin que esta se entere que es un singleton:

 

https://github.com/o...s.Singleton.pas

 

Esa implementacion a pesar de todo es mejorable. El problema que tiene es que asume que para instanciar el singleton, el constructor no tiene parametros. Lo ideal es que haya que "registrar" nuestro Singleton para que sepa como crearse. Eso es posible usando algun factory method, es decir, guardando un puntero a una funcion que al ejecutarse retorna el "T" creado

 

 

Con respecto a si usar la interface o la clase..  bueno no hay una respuesta. Yo siempre estoy a favor de usar interfaces si es posible. Pero hay que tener siempre en cuenta que si TInterfacedObject anda dando vueltas por ahi, lo mas correcto seria usar la interface.. o esperar a que tarde o temprano aparezcan los problemas raros

 

Yo creo que si me tomo la molestia de declarar las interfaces, y luego implementarlas (que hay que admitir que al momento de la codificacion es "tedioso") luego prefiero usar interfaces. Si decido no hacerlo, simplemente uso las clases. Ultimamente me estoy empezando a acostumbrar a eliminar los ".Create" del codigo por lo que si decido usar una interface, simplemente alguna factory me va a devolver la misma ya creada


Editado por Agustin Ortu, 01 julio 2016 - 01:41 .

  • 0

#13 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 01 julio 2016 - 01:47

Vi la implementación y me marea un poco el tema de los generics. No estoy muy acostumbrado a los <> :(

 

Entiendo el concepto, y la idea de tu propuesta. Puede que te la "robe" para más adelante :D

 

No te vayas demasiado por el lado de las interfaces. Usalas con moderación... no ya a ser cosa que termines como JAVA que se hace un mareo conceptual plagando por todo su arbolote interfaces. Hace tiempo en el foro tuvimos una discusión por el tema de si usamos o no habitualmente interfaces. Se terminó armando un buen forobardo :D

 

Saludos,


  • 1

#14 genriquez

genriquez

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 539 mensajes
  • LocationCali, Colombia

Escrito 01 julio 2016 - 01:50

Gracias Agustin Ortu y Delphius, he aprendido mucho de interfaces con ustedes.  algo que había delegado estrictamente a lo necesario.

 

Saludos.


  • 0

#15 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 01 julio 2016 - 02:03

Primero que nada me alegro Gustavo que haya quedado claro (a Marcelo no le digo nada, ya tiene las neuronas tan atrofiadas que no creo que pueda aprender nada nuevo  :D  :D )

 

Marce el repositorio esta ahi publico para que usen como quieran, asi que sin piedad!

 

He leido enterito el tema que dices de las interfaces. Por el momento me encuentro tranquilo. Mas que nada uso interfaces porque me gusta mucho la caracteristica (no se si es unica de Delphi, en algun lado lei que si, y que los javeros no la tienen). La misma tambien la expone si no me equivoco el propio genriquez en ese tema: hablo de implementacion por delegacion, que en Delphi se hace de forma muy facil y transparente usando la palabra clave implements

 

Entonces, basicamente lo que hago es crear clases usando composicion, implementando cada capsula de codigo de forma aislada (lo mas posible) pero luego juntando las piezas del rompecabezas

 

Apenas he usado Java. Lo poco que se, es mas por boca de terceros, o por anecdotas. Lo poco que usado es para "hacer el trabajito practico de la facu", y realmente nunca me ha picado la curiosidad de investigarlo como Delphi. Bueno, el resto es historia  ;)


  • 1

#16 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 01 julio 2016 - 03:40

Primero que nada me alegro Gustavo que haya quedado claro (a Marcelo no le digo nada, ya tiene las neuronas tan atrofiadas que no creo que pueda aprender nada nuevo  :D  :D )

 

Marce el repositorio esta ahi publico para que usen como quieran, asi que sin piedad!

 

He leido enterito el tema que dices de las interfaces. Por el momento me encuentro tranquilo. Mas que nada uso interfaces porque me gusta mucho la caracteristica (no se si es unica de Delphi, en algun lado lei que si, y que los javeros no la tienen). La misma tambien la expone si no me equivoco el propio genriquez en ese tema: hablo de implementacion por delegacion, que en Delphi se hace de forma muy facil y transparente usando la palabra clave implements

 

Entonces, basicamente lo que hago es crear clases usando composicion, implementando cada capsula de codigo de forma aislada (lo mas posible) pero luego juntando las piezas del rompecabezas

 

Apenas he usado Java. Lo poco que se, es mas por boca de terceros, o por anecdotas. Lo poco que usado es para "hacer el trabajito practico de la facu", y realmente nunca me ha picado la curiosidad de investigarlo como Delphi. Bueno, el resto es historia  ;)

 

Este perro viejo todavía puede aprender trucos nuevos... en unos días voy a asistir a un seminario (lástima que éste es sólo algo introductorio y solo parcialmente podré recabar cosas nuevas para mí) de uno temas que lo voy a mantener en el más estricto secreto.... por ahora. Tengo la esperanza de que cumplan la palabra y lo vayan ampliando y dando más contenidos a nivel profesional.

Y para el mes que viene (supuestamente está programado para Agosto) si se puede me anoto en un curso sobre desarrollo web para ir ampliando mis horizontes.

 

Yo tampoco se mucho de JAVA. Lo poco que se es por alguna que otra leída medio técnica, y alguito de código suelto que vi por las redes al que le reconozco medianamente algunas cosas como para portarlas a Delphi.

 

Saludos,


  • 1

#17 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 02 julio 2016 - 03:51

Otro caso mas que demuestra que mezclar referencias objetos/interfaces tiene problemas:
 
http://stackoverflow...after-iteration
 


delphi
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. var
  3. geometricFigures: TObjectList<TGeometricFigure>;
  4. geometricFigure: TGeometricFigure;
  5. begin
  6. ReportMemoryLeaksOnShutdown := true;
  7.  
  8. geometricFigures := TObjectList<TGeometricFigure>.Create();
  9. try
  10. geometricFigures.Add(TCircle.Create(4,2));
  11. geometricFigures.Add(TCircle.Create(0,4));
  12. geometricFigures.Add(TRectangle.Create(3,10,4));
  13. geometricFigures.Add(TSquare.Create(1,5));
  14. geometricFigures.Add(TTriangle.Create(5,7,4));
  15. geometricFigures.Add(TTriangle.Create(2,6,3));
  16.  
  17. for geometricFigure in geometricFigures do begin
  18. geometricFigure.ToString();
  19. end;
  20. finally
  21. //geometricFigures.Free(); -> porque esta linea no es necesaria?
  22. end;
  23. end;

 
Ahi mismo pueden encontrar mi respuesta en ingles, pero esta es la explicacion:
 
En este caso, se crea un objeto TObjectList pero no de las de la RTL de Delphi (es decir, no de la unidad Generics.Collections); se crea una Spring.Collections.Lists.TObjectList; es decir una clase que implementa las coleccion pero del framework Spring4D
 
El framework esta documentado y advierte que se deberia usar las colecciones siempre referenciandolas como interfaces: nunca se deberian crear directamente las clases que implementan las interfaces. Para crear cualquier collecion, se provee una clase estatica estilo factory que puede crear lo que se nos antoje, y siempre devuelve una referencia a una interface
 
Ahora, en el codigo problematico, se crea directamente un TObjectList, luego se le añaden algunos objetos, y por ultimo se los itera invocando al metodo .ToString para cada elemento
 
El codigo utiliza el bucle for..in para iterar los elementos. Para que una enumeracion Delphi soporte esa sintaxis, el objeto debe implementar un metodo GetEnumerator, que devuelve un objeto/record/interface que realiza la enumeracion
 
En Sping4D, ese metodo GetEnumerator esta implementado y devuelve una interface IEnumerator. Para construir el enumerador, se crea un TEnumerator y se pasa la coleccion como argumento; al hacer esto, la lista aumenta su contador de referencias en 1.
 
Cuando el bucle for..in termina, el enumerador es destruido: se invoca su destructor. El destructor de TEnumerator decrementa en 1 el contador de referencias de la lista
 
Ahora, la pregunta era: porque en este codigo no es necesario invocar a Free en la lista? Deberia producirse una fuga de memoria. La respuesta esta porque al referenciar a la variable como la clase y no la interface, nunca su contador de referencias paso a ser 1. Inspeccionando los atributos de la lista en tiempo de ejecucion con el depurador, vemos que el contador de referencias es 0.
 
Pero cuando se invoca al bucle for..in, que llama a GetEnumerator, que recible la lista, el contador de referencias pasa a 1
Cuando el for..in termina, el contador de referencias pasa a 0; cuando esto ocurre, se invoca al metodo _Release y la interface es destruida
 
Si en el codigo anterior se elimina el bucle for..in, se produce un memory leak

 

En fin, de nuevo, la moraleja es: No mezclar referencias a objetos con interfaces!!


  • 0

#18 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 02 julio 2016 - 05:58

Yo no estoy al tanto de muchas cosas nuevas en Delphi, sobre todo sobre ese framework.

Mi duda es ¿El compilador decide el tipo TObjectList a usar? ¿Si usa la de toda la vida, o la del framework? ¿En base a qué? ¿Cuando se emplea la tradicional, y cuando la de Spring4D?

¿Se puede forzar al compilador a que opte a uno por otro?

¿Que aporta ese Framework? ¿Ya viene instalado por defecto en Delphi 2010?

 

Saludos,


  • 0

#19 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 02 julio 2016 - 07:18

No, lamentablemente no viene por defecto con ningun Delphi. Pero es de codigo abierto y esta disponible para Delphi a partir de la version 2010 si no me equivoco.

 

Aporta un monton de cosas, tantas que apenas estoy rascando la superficie. La parte que uso siempre es su biblioteca de colecciones.

 

Las colecciones de Spring son mas versatiles que las de Delphi porque todas implementan una interface IEnumerable<T> (que no es la que viene en la RTL) que tiene metodos para soportar operaciones tipo LINQ, por ejemplo:


delphi
  1. program Project1;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. uses
  6. Spring,
  7. Spring.Collections,
  8. SysUtils;
  9.  
  10. procedure PrintAction(const Each: Integer);
  11. begin
  12. Writeln(IntToStr(Each));
  13. end;
  14.  
  15. // Spring.TPredicate<T> = reference to function (const Arg1: T): Boolean;
  16.  
  17. function GreaterThan(const AValue: Integer): Spring.TPredicate<Integer>;
  18. begin
  19. Result := function(const Each: Integer): Boolean
  20. begin
  21. Result := Each >= AValue;
  22. end;
  23. end;
  24.  
  25. var
  26. Lista, OtraLista: IList<Integer>;
  27. Elementos: IEnumerable<Integer>;
  28. begin
  29. // TCollections es una clase estatica que es es la fabrica de colecciones. Ella se encarga de crear la clase
  30. // que haga falta y devulve la interface
  31. Lista := TCollections.CreateList<Integer>;
  32.  
  33. // agregar datos
  34. Lista.Add(10);
  35. Lista.Add(15);
  36. Lista.AddRange([5, 32, 45]);
  37.  
  38. // el metodo for each que ejecuta una "accion" para cada elemento
  39. Writeln('Lista.ForEach');
  40. Lista.ForEach(
  41. procedure(const Each: Integer)
  42. begin
  43. Writeln(IntToStr(Each));
  44. end);
  45. Writeln;
  46.  
  47. // el metodo IEnumerable.Where, recibe como parametro un predicado y devuelve otro IEnumerable
  48. // un predicado es una funcion que devuelve un Boolean y se evalua por cada elemento de la coleccion
  49. // si evalua a True, ese elemento se encuentra en el resultado, si devuelve False no
  50.  
  51. // por ejemplo, para obtener la coleccion de elementos mayores o iguales que 20:
  52. Elementos := Lista.Where(
  53. function(const Each: Integer): Boolean
  54. begin
  55. Result := Each >= 20;
  56. end);
  57.  
  58. // puedo definir "aparte" la accion a ejecutar. Por ejemplo en un procedimiento "suelto", recibirlo por parametro
  59. // y luego enviarselo al .ForEach. Mismo caso para el .Where
  60. // imprimamos en pantalla esta nueva coleccion..
  61. Writeln('Elementos.ForEach(PrintAction)');
  62. Elementos.ForEach(PrintAction);
  63. Writeln;
  64.  
  65. // o tambien puedo encadenar operaciones, asi
  66. Writeln('Lista.Where(GreaterThan(20)).ForEach(PrintAction)');
  67. Lista.Where(GreaterThan(20)).ForEach(PrintAction);
  68. Writeln;
  69.  
  70. Writeln('Lista.Max: ' + IntToStr(Lista.Max));
  71. Writeln('Lista.Min: ' + IntToStr(Lista.Min));
  72. Writeln('Lista.First: ' + IntToStr(Lista.First));
  73. Writeln('Lista.Last: ' + IntToStr(Lista.Last));
  74.  
  75. Writeln;
  76.  
  77. // creo otra lista, en este caso le paso un arreglo con valores
  78. OtraLista := TCollections.CreateList<Integer>([70, 75, 80]);
  79.  
  80. // concatenacion de listas:
  81. Writeln('Lista.Concat(OtraLista).ForEach(PrintAction)');
  82. Lista.Concat(OtraLista).ForEach(PrintAction);
  83. Writeln;
  84.  
  85. Readln;
  86. end.

Y hay un monton de metodos mas: para ordenar (orden "comun" e inverso),

Skip(n) devuelve un una coleccion salteando los n primeros elementos,

SkipWhile(predicado) devuelve una coleccion salteando elementos mientras se cumpla el predicado, 

TakeWhile(predicado) devuelve una coleccion con los elementos que satisfacen el predicado, cuando encuentra uno que no satisface, deja de agregar al resultado

Any(predicado) devuelve True si al menos un elemento satisface el predicado, False en caso contrario

All(predicado) idem anterior pero en vez de uno, todos deben satisfacerlo

Contains(T) devuelve True si el elemento existe en la coleccion, false en caso contrario

 

A su vez, los metodos First, Last, Max, Min, y similares, poseen versiones sobrecargadas que permiten pasar comparadores proprios, o en el caso de First y Last, un predicado (dame el primer elemento que satisface el predicado)

 

Estas colecciones me recuerdan mucho a las de Smalltalk por ejemplo

 

Hay un monton de clases de colecciones implementadas: Listas, Listas Enlazadas, Colas, Pilas, Conjuntos, Mapas, Diccionarios,

 

Luego hay un Dependency Injection Container, que si no me equivoco es "lo mas famoso" o usado del framework. No lo he estudiado a fondo, pero estos contenedores lo que hacen es resolver las dependencias entre los objetos: basicamente conectan todas las clases/interfaces para que todo funcione

 

Hay una biblioteca criptografica; un ORM (Marshmallow, en el branch de pruebas), bibliotecas de reflexion, biblioteca para programacion orientada a aspectos (un interceptor, aspectos implementados como diversos loggers), utilidades para: :WinApi, SysUtils, IO.. tambien hay una implementacion de eventos multi-cast, nullables (NullableInteger, NullabeString, NullableDateTime, Nullable<T>.. etc). Luego hay un framework para MVC, del mismo autor, llamado DSharp que se espera que pronto se haga el merge en Spring

 

En fin, hay un monton de cosas implementadas y yo creo que es un paquete que deberia estar en cualquier entorno Delphi instalado


Editado por Agustin Ortu, 02 julio 2016 - 07:19 .

  • 1





Etiquetado también con una o más de estas palabras: Delphi, interface

IP.Board spam blocked by CleanTalk.