Ir al contenido


Foto

[RESUELTO] Class Property y destruccion de instancia


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

#1 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 06 marzo 2010 - 10:27

Saludos.

Llevo varios días con este problema y por más que le doy vueltas no logro conseguir una solución viable.

Como sabrán Delphi cuenta con este feature, creo que desde D2005, Class Property que nos permite acceder a la propiedad sin necesidad de instanciar la clase a quien pertenece.

Esto en si, lo que hace uso de los Class Methods.

Después de ese pequeño review, les explico mi problema.  Tengo una clase llamada TDmData que desciende de TDataModule con sus métodos y propiedades,  hasta aquí todo bien.

Ahora bien, tengo un formulario llamado TFrmCustomViewMDIData donde reside la Class Property con sus métodos y demás, este a su vez lo heredan los Mantenimientos.

Aquí parte de la declaración de TFrmCustomViewMDIData:


delphi
  1. TFrmCustomViewMDIData = class(TFrmCustomViewMDI)
  2. private
  3.     class function GetMyDmDataList(Indice: String): TDmData; static;
  4.     class procedure SetMyDmDataList(Indice: String; const Value: TDmData); static;
  5. protected
  6.     class var FDmDataStringList : TStringList;
  7.     class procedure CheckStateStringList;
  8. public
  9.     //Propiedad para listar los DataModule que necesita adicionales
  10.     class property MyDmDataList[Indice : String] : TDmData read GetMyDmDataList write SetMyDmDataList;



La implementación de los métodos:


delphi
  1. class procedure TFrmCustomViewMDIData.CheckStateStringList;
  2. begin
  3.   if not Assigned(FDmDataStringList) then
  4.     FDmDataStringList := TStringList.Create;
  5. end;
  6.  
  7. //Ojo con este metodo
  8. procedure TFrmCustomViewMDIData.FormDestroy(Sender: TObject);
  9. begin
  10.   inherited;
  11.   FDmDataStringList.Free;
  12.   FDmDataStringList := Nil;
  13. end;
  14.  
  15. class function TFrmCustomViewMDIData.GetMyDmDataList(Indice: String): TDmData;
  16. begin
  17.   CheckStateStringList;
  18.   Result := Nil;
  19.   if (not (Trim(Indice) = NullAsStringValue)) and (FDmDataStringList.IndexOf(Indice) >= 0) then
  20.     Result := FDmDataStringList.Objects[FDmDataStringList.IndexOf(Indice)] as TDmData;;
  21. end;
  22.  
  23. class procedure TFrmCustomViewMDIData.SetMyDmDataList(Indice: String;
  24.   const Value: TDmData);
  25. begin
  26.   CheckStateStringList;
  27.   if FDmDataStringList.IndexOf(Indice) < 0 then
  28.     FDmDataStringList.AddObject(Indice, Value)
  29.   Else
  30.     if TDmData(FDmDataStringList.Objects[FDmDataStringList.IndexOf(Indice)]) <> Value then
  31.       FDmDataStringList.AddObject(Indice, Value);
  32. end;



Como habrán notado utilizo un TStringList para almacenar los DataModules y luego poder buscarlo por su nombre de clase. 

¿Donde esta el problema? En que "al parecer" la instancia del TStringList es global para todos, porque cuando abro dos formularios hijos de TFrmCustomViewMDIData al cerrar uno cualquiera destruye la instancia del TStringList como esta en el FormDestroy.  Provocando un error de memoria cuando utilizo el formulario que quedo abierto de los dos cuando este accede a la propiedad que contiene el TStringList .

Espero haberme explicado correctamente. echenme una mano.
  • 0

#2 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 06 marzo 2010 - 11:44

Hola Rolphy:

Me he llevado una sorpresa al saber que existían las class properties y por lo que veo también las class variables, ya que no tenía una versión de Delphi con dichas características, hace poco adquirí el RADStudio 2010 así que voy a ponerme las pilas, aunque por desgracia la ayuda deja mucho que desear  :sad:, y no explica nada al respecto (o al menos yo no lo he podido encontrar).

De todas formas, como tú bien dices, el problema está localizado, y según la lógica de los métodos / variables / propiedades de clase, éstos actúan a nivel de clase, no de instancia, a modo de variables globales. Me parece un buen avance, pero que hay que usar con precaución. El tener una variable de tipo TObject como en tu caso crea el problema de ¿quién se encarga de destruirlo?. Ten en cuenta que incluso podría no existir ninguna instancia de dicha clase y que por lo tanto no se ejecutara nunca ningún Destroy y ese StringList quedara sin liberar.

Lo que suele emplearse en estos casos es acudir a la sección finalization, al final de la unit (acuérdate de que el compilador necesitará otra sección initialization anterior a ésta, aunque no lleve nada de código), donde puedes acceder a dicha variable y liberarla, o más elegante aún llamar a otro método de clase que se encargue de liberarla.

Aunque sinceramente la solución que más me gusta, y la que no tenía más remedio que emplear con versiones anteriores de Delphi, es la de declarar dicha variable dentro de la sección Implementation (por lo tanto oculta al resto de units), luego había una función suelta como la que tú empleas CheckStateStringList que se encargara de crear la instancia cuando es requerida, y alguna que otra función suelta encargada de asignarle y recuperar valores. Pero esto ya es cuestión de gustos.

Saludos,
  • 0

#3 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 06 marzo 2010 - 11:59

Saludos.

¿Entonces tendré que crear el objeto StringList para cada unidad al inicializar?

Si es así, veo que tendré objetos en algunas unidades sin necesidad de usarlo y no quiero tener memoria ocupada sin necesidad.
  • 0

#4 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 06 marzo 2010 - 12:09

Hola,

No sucederá eso en ningún caso, ya que, si optas por declarar FDmDataStringList como variable global (aunque definida en Implementation para ocultarla al resto de units), sólo tendras UNA instancia, y si optas por dejarla como una propiedad de clase, igualmente tendrás UNA instancia, en ambos casos se comporta como una variable global.

No entiendo por qué dices que lo tendrás en varias unidades. En realidad el objeto sólo existirá allá donde lo declares, sea como variable en una unit, o sea como variable en una clase.
  • 0

#5 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 06 marzo 2010 - 12:35

¿Entonces tendré que crear el objeto StringList para cada unidad al inicializar?


Respondiendo a esta pregunta: no hace falta que el objeto se cree al inicializar la unit (en realidad la sección initialization hace falta en este caso únicamente para poder poner a continuación la sección finalization, porque así lo exige Delphi). La instancia se crearía como lo tenías hasta ahora, bajo demanda al llamar a CheckStateStringList, lo que sí cambiamos es el momento de destruirla, cuando se cierra la aplicación y Delphi ejecuta el código de finalization de la unit.

Lo que sí puede realizar cada instancia de TFrmCustomViewMDIData al destruirse, es eliminar de dicha lista su objeto DMData, para aligerar la lista de información innecesaria, pero la lista como tal, una vez creada, permanecería viva hasta finalizar el programa, aunque estuviera vacía de elementos, esto no creo que suponga mucho derroche de recursos. O incluso el método que se encargue de eliminar un elemento, compruebe si no queda ninguno (Count = 0) y en se caso la destruya, acordándote de ponerla a nil.

(ahora que lo pienso, quizás esto es lo que querías en un principio, en vez de destruirla directamente, hacerlo sólo cuando no le queden elementos, jeje)



delphi
  1. procedure TFrmCustomViewMDIData.FormDestroy(Sender: TObject);
  2. begin 
  3.   BorrarDataDelStringList(DMData);
  4.   if FDmDataStringList.Count = 0 then
  5.   begin 
  6.     FDmDataStringList.Free; 
  7.     FDmDataStringList := Nil;
  8.   end;
  9.   inherited; 
  10. end;




Espero haberme explicado,

Saludos.
  • 0

#6 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 06 marzo 2010 - 01:52

¡Hola!

Rolphy, formalmente lo que buscas es un singleton.
Andres te está dando una muestra sencilla y práctica de como realizarlo. En una ocasión participé de un buen debate sobre esto en clubdelphi. Aquí te dejo el enlace por si te interesa.

Saludos,
  • 0

#7 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 06 marzo 2010 - 02:04

Saludos.

Gracias andres1569 y Delphius por su interés.

Pero realmente no ando buscando un Singleton, sino más bien que cada TFrmCustomViewMDIData sea dueño de su FDmDataStringList y lo libere cuando el mismo se cierre.

Creo que esa es la manera más optima de utilización de recursos, pues ahí es donde tengo el problema de que al liberar el formulario este se lleva la instancia que no le pertenece.  NO quiero una instancia global, ese es el problema que tengo que se esta manejando como global y no entiendo porque rayos lo hace.

¿Pueden ayudarme a que no sea "global" de que TFrmCustomViewMDIData sea dueño de FDmDataStringList ?

Gracias.
  • 0

#8 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 06 marzo 2010 - 04:14

Hola Rolphy,
Yo no se del concepto de class property ni de class var... Esas características están fuera mi alcance porque yo uso D6 y no manejo el concepto.

Creo entonces que el problema está justo en eso... que no deben ser class var, sino un atributo normal como cualquiera. Como he dicho, no se como y para qué sirve tener un class var y un class property.

Si lo que se busca es que cada TFrmCustomViewMDIData tenga referencia a su propio FDmDataStringList entonces debe haber un atributo con el cual hacer referencia... y el asunto es que a mi el sentido común me indica que no se puede hacer referencia a algo si no se tiene el adecuado acceso a la instancia.

¿Cuál es el propósito de que sea Class? Si nos puedes explicar el concepto de class var y class property quizá pudiera comprender mejor que es lo que se consigue con esto.

A mi me dice que un class var es como una variable asociada a una clase. Si es así, es de esperar que exista una y sólo una variable (y por tanto instancia) de FDmDataStringList puesto que está asociada a la clase... y como clase sólo hay una: TFrmCustomViewMDIData, no tenemos dos, ni tres clases TFrmCustomViewMDIData. Tu lo que buscas es que cada instancia de TFrmCustomViewMDIData (objeto) tenga su propio FDmDataStringList.... y como este es para la clase es lógico pensar que existe uno sólo... todas las instancias de este StringList que has creado en cada CustomView (disculpa que acorte los nombres) han estado sobreescribiendo a esa única variable de clase.

Eso explicaría su comportamiento global.

No se si me se me entiende.

Si mi intuición está en lo cierto, entonces es allí el problema. Deberás cambiar ese class var por los típicos atributos de siempre.

EDITO:
Justo ahora recuerdo que en clubdelphi había leido algo de esto... Y leyendo mi presunción está en lo correcto: una variable de clase es única, y por tanto a cada instancia le resulta ser como una variable global. El hilo es éste. Lee justo el post 11.

Saludos,
  • 0

#9 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 06 marzo 2010 - 07:37

Me vais a permitir que cambie un poco de tema, o mejor de lengua.

En C++ el concepto de Class Property y Class Methods están desde sus orígenes. Se denominan miembro estáticos de la clase, no precisan instanciar un objeto para existir y son comunes a todos ellos. Un miembro static sólo puede ser modificado por una función también static.

La inicialización de un miembro static debe realizarse fuera de la clase a la que pertenece, no en el constructor ni en la cabecera, y por lo tanto no debe ser destruida en el destructor de un objeto puesto que no le pertenece a él sino a la clase.

Los miembros static (Class Property de delphi) realmente se comportan como variables globales a las que se accede a través de la clase aún sin existir objetos de la misma por lo que su ámbito de existencia es toda la aplicación, se declaran en la clase, se inicializan fuera y se destruyen con la aplicación.

Espero haber esclarecido algo en el tema.

Saludos.


  • 0

#10 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 08 marzo 2010 - 03:45

La inicialización de un miembro static debe realizarse fuera de la clase a la que pertenece, no en el constructor ni en la cabecera, y por lo tanto no debe ser destruida en el destructor de un objeto puesto que no le pertenece a él sino a la clase.


Este párrafo resume muy bien lo que no debe hacerse con un class property.

Pero realmente no ando buscando un Singleton, sino más bien que cada TFrmCustomViewMDIData sea dueño de su FDmDataStringList y lo libere cuando el mismo se cierre.


Aquí no aclaras si te refieres a cada instancia de TFrmCustomViewMDIData , o a la clase en sí, pero dejas entrever que es lo primero ya que una clase no se cierra. Pero en ese caso el código que pusiste debería prescindir de las propiedades de clase y sustituirla por una propiedad del objeto en sí ... Según lo que quieras lograr, posiblemente haya que plantearlo de otra forma.

Saludos,

Andrés
  • 0

#11 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 08 marzo 2010 - 06:57

Saludos.

Me disculpan por haberme desconectado desde el sábado,  es que con este problema merodeando mi cabeza decidí mejor no prender la PC por el día del domingo para tomarme un descanso.

Veo que tendré mucho más problemas de lo imaginado si tengo que cambiar de Class Property a Property "Simple" porque hago asignación de los DataModules adicionales con los que un formulario (heredero de TFrmCustomViewMDIData) debe de buscar sus datos adicionales.

Cuando me refiero a adicionales es, cuando se crea una instancia del CustomView (gracias Delphius por el acorte) le paso con que DataModule el debe de trabajar pero si existe por ejemplo que se debe seleccionar la Categoría de Clientes pues es un "DataModule adicional" y se lo asigno al formulario vía la Class Property, luego una Action se encarga de instanciar el formulario y realiza una serie de operaciones.

Entonces si debo de cambiar a una propiedad simple, la mayor parte de la lógica del instanciamiento que tengo ya plasmada pues desaparece creándome un gran lío.

No puedo utilizar el concepto de "variable global" con FDmDataStringList porque me compartirían los DataModules y existen formularios que usan los mismos DataModules al ser una aplicación MDI; cuando me mueva entre registros de un DataModule común se verán afectados todos los formularios abiertos.

¿Pueden ayudarme a conseguir una mejor lógica y tratar de que no se rompa mucho la existente?
  • 0

#12 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 08 marzo 2010 - 07:32

Hola de nuevo, Rolphy:

Creo haber empezado a entenderte. Tan sólo tres preguntas: ¿tienes en el proyecto formularios heredados de TFrmCustomViewMDIData, por ejemplo TFrmCustomViewMDIData1, TFrmCustomViewMDIData2 ... etc? Ojo, no me refiero a que crees diferentes instancias del mismo.

¿quieres que cada uno de esos tipos de formularios (propiamente sería clase de formulario) almacene su propia lista de DataModules?

Cuando dices que no quieres que se compartan los DataModules, ¿quiere esto decir que los DataModules necesarios para cada formulario los creas "al vuelo"?

Saludos
  • 0

#13 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 08 marzo 2010 - 07:39

Gracias andres1569 por tu interés.

¿tienes en el proyecto formularios heredados de TFrmCustomViewMDIData, por ejemplo TFrmCustomViewMDIData1, TFrmCustomViewMDIData2 ... etc? Ojo, no me refiero a que crees diferentes instancias del mismo.


Sí tengo muchos formularios heredados de el, el mismo es la base de los formularios que sirven para mantenimientos.

¿quieres que cada uno de esos tipos de formularios (propiamente sería clase de formulario) almacene su propia lista de DataModules?


Es justamente lo que ando buscando, que cada formulario sea dueño de su lista.

Cuando dices que no quieres que se compartan los DataModules, ¿quiere esto decir que los DataModules necesarios para cada formulario los creas "al vuelo"?


Sí, como cree un Action (ActionList) ahí le digo en una propiedad tipo TStringList, mira estos son los DataModules que TFrmCustomViewMDIData1 necesita así que cuando se vaya a crear instancia esos DataModules y pónselos en su listado.

Espero haberme expresado bien.
  • 0

#14 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 08 marzo 2010 - 07:53

Yo creo que deberías seguir insistiendo en Class Property. Sólo tienes que crear esa propiedad desde un Class Method no desde un objeto existente. Luego sencillamente no lo destruyes, se destruirá con la aplicación.

Saludos.
  • 0

#15 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 08 marzo 2010 - 07:56

Yo creo que deberías seguir insistiendo en Class Property. Sólo tienes que crear esa propiedad desde un Class Method no desde un objeto existente. Luego sencillamente no lo destruyes, se destruirá con la aplicación.

Saludos.


Saludos.

Perdona mi ignorancia o puede que sea nublazon del cerebro.....

¿Puedes explicarte un poco mejor?
  • 0

#16 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 08 marzo 2010 - 08:25

Bueno, en este caso, como dice Escafandra, parece que lo mejor es usar los class properties, pero uno por cada formulario heredado de TFrmCustomViewMDIData . La clase ancestra, TFrmCustomViewMDIData, no declararía ningúna lista sino un método de clase virtual llamámosle GetMyDataStringList, que cada clase hija se encargaría de redefinir para devolver su propia lista.

Algo como esto:



delphi
  1. TFrmCustomViewMDIData = class(TFrmCustomViewMDI)
  2. private   
  3.   class function GetMyDmDataList(Indice: String): TDmData; static;   
  4.   class procedure SetMyDmDataList(Indice: String; const Value: TDmData); static;
  5. protected   
  6.   class function GetMyDataStringList : TStringList;    virtual;
  7. public    //Propiedad para listar los DataModule que necesita adicionales   
  8.   class property MyDmDataList[Indice : String] : TDmData read GetMyDmDataList write SetMyDmDataList;
  9. end;
  10.  
  11. // En la lase descendiente
  12.  
  13. TFrmCustomViewMDIData1 = class(TFrmCustomViewMDIData)
  14. private
  15.   class var FDmDataStringList : TStringList;
  16. protected   
  17.   class function GetMyDataStringList : TStringList;    override;
  18.   class procedure DeleteDataStringList;
  19. end;
  20.  
  21. IMPLEMENTATION
  22.  
  23. class function TFrmCustomViewMDIData.GetMyDmDataList(Indice: String): TDmData;
  24. var
  25.   MyList : TStringList;
  26. begin 
  27.   Result := Nil;   
  28.   MyList  := GetMyDataStringList;  // AQUÍ LLAMAMOS A LA FUNCION VIRTUAL
  29.   if (not (Trim(Indice) = NullAsStringValue)) and (MyList.IndexOf(Indice) >= 0) then   
  30.   Result := MyList.Objects[MyList.IndexOf(Indice)] as TDmData;
  31. end;
  32.  
  33. class procedure TFrmCustomViewMDIData.SetMyDmDataList(Indice: String;  const Value: TDmData);
  34. var
  35.   MyList : TStringList;
  36. begin 
  37.   MyList  := GetMyDataStringList;  // AQUÍ LLAMAMOS A LA FUNCION VIRTUAL
  38.   if MyList.IndexOf(Indice) < 0 then MyList.AddObject(Indice, Value) 
  39.   Else if TDmData(MyList.Objects[MyList.IndexOf(Indice)]) <> Value then
  40.   MyList.AddObject(Indice, Value);
  41. end;
  42.  
  43. (...)
  44.  
  45. class function TFrmCustomViewMDIData1.GetMyDataStringList : TStringList;   
  46. begin
  47.   if FDmDataStringList = nil then FDmDataStringList := TStringList.Create;
  48.   result := FDmDataStringList;
  49. end;
  50.  
  51. class procedure TFrmCustomViewMDIData1.DeleteDataStringList;   
  52. begin
  53.   FDmDataStringList.Free;
  54.   FDmDataStringList := nil;
  55. end;
  56.  
  57. (...)
  58.  
  59. INITIALIZATION
  60.  
  61. FINALIZATION
  62.   TFrmCustomViewMDIData1.DeleteDataStringList; // liberamos la lista desde fuera de la clase



esto supone tener que añadir más código, por cada descendiente hay que repetir los mismos pasos, pero creo que no hay más remedio.

Lo que aún no me queda claro es si al cerrar un formulario quieres que elimine de la lista los DataModules asociados o si estos permenecerán disponibles para otros futuros formularios que heredan de la misma clase. Si deseas que los elimine, entonces en el Destroy, que recorra la lista y borre los elementos que le pertenecen, pero EN NINGUN CASO destruya la lista (de esto se encargará la aplicación al finalizar llamando al FINALIZATION de la unit correspondiente).

Saludos

  • 0

#17 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 08 marzo 2010 - 08:57

Saludos.

Vaya vaya realmente tendré que analizarlo bien, porque como quiera es una pega.

Este ultimo "método" prácticamente no me rompe el código actual, pero tengo que estar redefiniendo por cada hijo.

Gracias Escafandra y andres1569 por sus ayudas.
  • 0

#18 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 09 marzo 2010 - 07:28

Saludos.

Después de un arduo análisis decidí  mejor cambiar el enfoque inicial y pasar a tener en vez de un Class Property a un "simple" Property, aunque esto me cueste cambiar el código.

Prefiero cortar por lo sano ahora que el proyecto no esta tan grande y después se me presente otra situación donde no lo pueda manejar con un par de If como lo tengo ahora.  Además de que prefiero optimizar el uso de recursos y minimizar código.

Así que pasare el hilo a Resuelto a sabiendas de que debo cambiar el enfoque para su solución.

Gracias a todos por sus interés.
  • 0

#19 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 09 marzo 2010 - 01:01

Hola Rolphy,

No sabría decirte hasta que punto es mejor optar una u otra opción.
De lo que recuerdo de los otros hilos en que preguntaste y que te intervinieron estas clases puedo ver que la "cosa" es bastante compleja.

Quizá sea bueno y sano poner en discusión una descripción más global de lo que estás encarando. Si nos apoyamos en UML para ilustrar algunas cosas quizá sea posible ver como encarar y unir todas las piezas del rompecabezas que estás armando.
Claro está, si es que te interesa y estás en la libertad de brindarnos más información al respecto. Si es algo del trabajo y/o privado es entendible que no nos puedas contar.

Saludos,
  • 0

#20 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 09 marzo 2010 - 01:10

Saludos.

Delphius gracias por ofrecerte a ayudarme con esto.

Antes debería de sacar tiempo para eso del UML, y si puedo compartirlo es un desarrollo personal y se trata de construir una "plataforma" (FrameWork) para futuros proyectos que pueda realizar desde mi casa.
  • 0




IP.Board spam blocked by CleanTalk.