Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   OOP (https://www.clubdelphi.com/foros/forumdisplay.php?f=5)
-   -   Como crear clases correctamente? (https://www.clubdelphi.com/foros/showthread.php?t=67252)

alquimista 07-04-2010 23:06:13

Como crear clases correctamente?
 
Bueno..
No tengo mucha idea de usar clases y he intentado crear algunas.
El problema es que cuando asigno varias variables de la misma clase si creo y destruyo una de las dos variables me cargo los datos de la otra. No se porque.

Adjunto la unidad que he creado a ver si algun sabio de los de aqui me ayuda a ver que es lo que esta mal. Y como hacerlo mejor.
Un saludo..

Código:

unit pathmovil;

interface

uses Windows,graphics,extctrls,controls,math;

CONST
  MAXSEQ=512;
  MAXTRAD=2;
  MAXREC=50;
  PIXELSMETRO=200; // un metro recorrido por el robot equivale en pantalla a 200 pixels
  NULLREC=$FFFF;
 type
  TTipoMov=(mvStop,mvAdelante,mvAtras,mvDerecha,mvIzquierda,mvNoreste,mvsureste,mvsuroeste,mvnoroeste);

    TTradMovil=record
    Movi: TTipoMov;
    TiempoMov : TTime;
    end;

 Tpathmovil = record
    Origenx, Origeny, Destinox, Destinoy : Integer;
    Seleccion: integer;
    TiempoPath: cardinal;
    CteMetro, CteGiro: Cardinal;
    Tradmov: Array [1..MAXTRAD] of TTradMovil;
  end;

  TSecuencia =class
  public
    Numero : integer;
    Nombre :String;
    Descripcion: String;
    Tiempo: cardinal;
    Color: TColor;
    Recorr : TpathMovil;
    constructor Create;
    destructor Destroy;
    function Add(var MIrec:Tpathmovil):integer;
    function Delete(Indice:integer):integer;
    procedure SetOrigen(x,y:integer);
    procedure SetDestino(xx,yy:integer);
    procedure SetOriDest(x,y,xx,yy:integer);
    procedure SetCtes(Ct,Cg: cardinal);
    function GetDeltaX:integer;
    function GetDeltay:integer;
    function GetModulo:double;
//    function GetAngulo:double;
    function GetTimems:double;
    function GetLonmetros:double;
    procedure SetColor(ClColor:TColor);
    procedure DibujarRecorrido(x,y:integer;Canvas:Tcanvas);
    function  Rotate2D(x,y:integer; alpha:double): TPoint;


    private
    Count:integer;
  end;


implementation

constructor Tsecuencia.Create;
begin
  inherited;
  //Count:=0;
  Numero:=0;
  Nombre:='';
  Descripcion:='';
  Tiempo:=0;
  SetOriDest(NULLREC,NULLREC,NULLREC,NULLREC);

  //Metodo create de recorrido
end;
destructor Tsecuencia.Destroy;
begin
  inherited;
end;
procedure TSecuencia.SetColor(ClColor:TColor);
begin
  Color:=clColor;
end;

function Tsecuencia.Add(var MIrec:Tpathmovil):integer;
begin
(* inc(count);
 if Count<=MAXREC then
  begin
 //  if Recorr[Count]=nil then
 //    Recorr[Count]:=Tpathmovil.Create;
  *)
  Recorr:=Mirec;
  (* result:=Count;
  end
 else result:=-1  *)
end;
function Tsecuencia.Delete(Indice:integer):integer;
 begin
  if (indice>0) or (indice<=Count) then
      begin
        // hacer rutina de borrado de elemento

      end
  else result:=-1;

 end;

 function Tsecuencia.GetModulo:double;

var
dx: Double;
dy: Double;
begin
dx := GetDeltax;
dy := GetDeltay;
Result := Sqrt(dx * dx + dy * dy);
end;

function TSecuencia.GetTimems:double;
begin
  result:=(Getmodulo*Recorr.CteMetro)/PIXELSMETRO //  cte tiempo para 9 Voltios 1metro lo recorre en 4000 msegundos
end;

function TSecuencia.GetLonmetros:double;
begin
    result:=(Getmodulo/PIXELSMETRO) // 1 metro 200 pixels
end;


procedure Tsecuencia.SetOrigen(x,y:integer);
begin
    Recorr.Origenx:=x;
    Recorr.Origeny:=y;
end;

procedure Tsecuencia.SetDestino(xx,yy:integer);
begin
    Recorr.Destinox:=xx;
    Recorr.Destinoy:=yy;
end;
procedure Tsecuencia.SetOriDest(x,y,xx,yy:integer);
begin
    Recorr.Origenx:=x;
    Recorr.Origeny:=y;
    Recorr.Destinox:=xx;
    Recorr.Destinoy:=yy;
end;

procedure TSecuencia.SetCtes(Ct,Cg: cardinal);
begin
  Recorr.CteMetro:=Ct;
  Recorr.CteGiro:=Cg;
end;

function TSecuencia.GetDeltaX:integer;
begin
  result:=(Recorr.Destinox-Recorr.Origenx);
end;
function TSecuencia.GetDeltay:integer;
begin
  result:=(Recorr.Destinoy-Recorr.Origeny);
end;
procedure TSecuencia.DibujarRecorrido(x,y:integer; Canvas:Tcanvas);
 begin
  with Canvas do
  begin
      Pen.Style :=psdot;
      Pen.color:=Color;
      with Recorr do
      begin
        if Origenx<>NULLREC then
          begin
            MoveTo(Origenx+x,Origeny+y);
            LineTo(x+Destinox,y+Destinoy);
          end;
      end; //with ..
  end;
end;


//valores 0 a 360 antihorario o 0 180 0 -180
function Tsecuencia.Rotate2D(x,y:integer; alpha:double): TPoint;
var
  sinus, cosinus : Extended;
begin
  alpha:=DegToRad(alpha);
  SinCos(alpha, sinus, cosinus);

  result.x := Round(x*cosinus + y*sinus);
  result.y := Round(-x*sinus + y*cosinus);
end;

end.

Unidad USprites

Código:

unit sprites;

interface
uses Windows,SysUtils,graphics,extctrls,pathmovil,classes;

type
  TOrientacion=(orNorte,orNorEste,orEste,orSureste,orSur,orSurOeste,orOeste,OrNoroeste);

type
  TSprite = class
  public
    x, y, xAnterior, yAnterior: Integer;
    ColorTransparente: TColor;
    Imagen, Mascara,buffer: TImage;
    Seleccion: integer;
    deltx,delty:integer;
    Orientacion:TOrientacion;
    Secuencia: Array [1..MAXSEQ] of TSecuencia;
    constructor Create;
    destructor Destroy; override;
    procedure Cargar( sImagen: string );
    procedure Dibujar( x,y:integer; Canvas: TCanvas );
    function AddSec(MIsec:TSecuencia):integer;
    function Delete(Indice:integer):integer;

    private
      Count: integer;
  end;



implementation

constructor TSprite.Create;
begin
  inherited;
  Imagen := TImage.Create( nil );
  Imagen.AutoSize := True;
  Mascara := TImage.Create( nil );
  ColorTransparente := RGB( 255, 0, 255 );
  Buffer :=  TImage.Create( nil );
  Count:=0;
  end;

destructor TSprite.Destroy;
begin
  Buffer.Free;
  Mascara.Free;
  Imagen.Free;
  inherited;
end;

procedure TSprite.Cargar( sImagen: string );
var
  i, j: Integer;
begin
  Imagen.Picture.LoadFromFile( sImagen );
  Mascara.Width := Imagen.Width;
  Mascara.Height := Imagen.Height;
  /// jcsoft
  Buffer.Width:=  Imagen.Width;
  Buffer.Height := Imagen.Height;
    ///  jcsoft


  for j := 0 to Imagen.Height - 1 do
    for i := 0 to Imagen.Width - 1 do
      if Imagen.Canvas.Pixels[i,j] = ColorTransparente then
      begin
        Imagen.Canvas.Pixels[i,j] := 0;
        Mascara.Canvas.Pixels[i,j] := RGB( 255, 255, 255 );
      end
      else
        Mascara.Canvas.Pixels[i,j] := RGB( 0, 0, 0 );

        end;

procedure TSprite.Dibujar( x, y: Integer; Canvas: TCanvas );
begin
  Canvas.CopyMode := cmSrcAnd;

  Canvas.Draw( x, y, Mascara.Picture.Graphic );
  Canvas.CopyMode := cmSrcPaint;
  Canvas.Draw( x, y, Imagen.Picture.Graphic );
end;
function TSprite.AddSec(MIsec:TSecuencia):integer;
begin

 if Count<=MAXSEQ then
  begin
  inc(count);
  if Secuencia[Count]=nil then
    Secuencia[Count]:=TSecuencia.Create;
  miSEC.numero:=Count;
  if Misec.NOmbre='' then
      MIsec.Nombre:='SEC'+IntToStr(Count);
  Secuencia[Count]:=Misec;
  result:=Count;
  end
 else result:=-1
end;
function TSprite.Delete(Indice:integer):integer;
 begin
  if (indice>0) or (indice<=Count) then
      begin
        // hacer rutina de borrado de elemento
         
      end
  else result:=-1;

 end;
end.


¿Podria ser porque en el programa principal declaro como privada la estructura ListaSprites?
Código:

type
  TForm1 = class(TForm)
    ........
  private
  ListaSprites: array [1..MAXBOTS] of TSprite;
    ...

Si necesitais mas datos no hay problema para darlos..

Lepe 08-04-2010 09:58:38

Yo veo un problema aqui:
Código Delphi [-]
function TSprite.AddSec(MIsec:TSecuencia):integer;
begin

 if Count<=MAXSEQ then
   begin
   inc(count);
   if Secuencia[Count]=nil then
     Secuencia[Count]:=TSecuencia.Create; <--- misma variable asignada
   miSEC.numero:=Count;
   if Misec.NOmbre='' then
      MIsec.Nombre:='SEC'+IntToStr(Count);
   Secuencia[Count]:=Misec;  <---- misma variable asignada
   result:=Count;
   end
 else result:=-1
end;
Si secuencia[count] es nil, estás creando un nuevo objeto en memoria. Posteriormente haces: Secuencia[count] := Misec; eso quiere decir que el Objeto que creaste en memoria se pierde, es decir, estás creando memoria que nunca se podrá liberar.

En principio hay dos soluciones que depende de tu criterio:
- Copiar los datos de Misec al nuevo objeto recien creado (secuencia[Count].Nombre := Misec.Nombre)
- No crear el objeto en memoria y dejar sólo la línea Secuencia[count] := Misec. Pero esto puede tener problemas si más tarde liberas el objeto... por ejemplo, el siguiente código dará problemas:
Código Delphi [-]
var sec :TSecuencia
    sprite:TSprite;
begin
   sec := TSecuencia.create; 
   sprite := TSprite.Create;
   sprite.AddSec(sec);
   sec.Free; --> problema cuando accedas a esa secuencia a través de sprite, Access violation.

Otra variación es usar un TObjectList en lugar de arrays (busca en el foro por Tobjectlist)

alquimista 08-04-2010 10:25:01

Ya intente lo de los TObjectList pero se me queda grande, ya que no controlo mucho lo de las clases y me daba Acces violation por muchos sitios. Ya pergunte sobre eso en foro varios.

LO que puse
Código:

  if Secuencia[Count]=nil then
    Secuencia[Count]:=TSecuencia.Create; <--- misma variable asignada

Era para evitar que si no habia una secuencia[x] creada me diera el error de violation al asignar luego a misec.


Cita:

- No crear el objeto en memoria y dejar sólo la línea Secuencia[count] := Misec. Pero esto puede tener problemas si más tarde liberas el objeto... por ejemplo, el siguiente código dará problemas:
Asigno el constructor de las dos variables
ListaSprites[SelectedSprite].Secuencia[x]:=TSecuencia.Create;
TmpSec:=TSecuencia.Create;
..relleno datos en tmpSec...
ListaSprites[SelectedSprite].Secuencia[x].AddSec(TmpSec);

TmpSec.Free; // o Destroy???
¿Esto es correcto?


antes de usar la funcion AddSec

El poner todo el codigo es para saber si he hecho alguna cosa que este mal puesta de base.
PD: Ya se que hacer arrays y clases no parace muy ortodoxo. Ya me gustaria saber Delphi como algunos. pero es lo que hay...


PD 2:La firma me encanta: Es lo que muchas veces suele pasar en la vida real.

Ñuño Martínez 08-04-2010 12:29:11

Unos detallitos:
Código Delphi [-]
VAR
  Variable: TClase;
El código anterior define una referencia a un objeto. No es un objeto, sino una referencia. El objeto no existe hasta que lo creas.

Código Delphi [-]
  Variable := TClase.Create;
Con esto estás creando un objeto, no "asignando el constructor de la variable".:rolleyes: Ahora "Variable" hace referencia al objeto recién creado.

Código Delphi [-]
  Variable1 := TClase.Create;
  Variable2 := Variable1;
  Variable2.Trabaja;
Con esto Variable1 y Variable2 hacen referencia al mismo objeto. No estás clonando (copiando) el objeto. Al ser referencias al mismo objeto, ejecutar "Variable2.Trabaja" es exactamente lo mismo que ejecutar "Variable1.Trabaja". Recuerda lo que he dicho al principio: Son referencias a objetos, no objetos.

Código Delphi [-]
  Variable1 := TClase.Create;
  Variable2 := Variable1;
  Variable1.Free;
  Variable2.Trabaja;
Si hemos dicho que Variable1 y Variable2 hacen referencia al mismo objeto, resulta lógico pensar que tras ejecutar "Variable1.Free" tampoco podemos usar Variable2, ya que el objeto al que referencia no existe, por lo que resulta en una violación de acceso.

Por cierto: nunca hay que llamar al destructor (Destroy). Para destruir un objeto hay que utilizar "Free".

Código Delphi [-]
  PROCEDURE Ejemplo;
  VAR
    Objeto: TClase;
  BEGIN
    Objeto: TClase.Create;
  END;
En el código anterior, definimos una referencia a un objeto y luego lo creamos, pero no lo destruimos. Al terminar el procedimiento, la refencia al objeto es eliminada, pero el objeto sigue existiendo. Por eso podemos hacer cosas como la siguiente:

Código Delphi [-]
  FUNCTION CreaObjeto: TClase;
  VAR
    Objeto: TClase;
  BEGIN
  { Crea el objeto. }
    Objeto: TClase.Create;
  { Devuelve la referencia al objeto. }
    RESULT := Objeto;
  END;

  PROCEDURE Trabaja (aObjeto: TClase);
  BEGIN
  { Utilizamos la referencia recibida. }
     aObjeto.HazAlgo;
  END;

VAR
  ReferenciaObjeto: TClase;
BEGIN
   ReferenciaObjeto := CreaObjeto;
   Trabaja (ReferenciaObjeto);
   ReferenciaObjeto.Free;
END.

alquimista 08-04-2010 14:19:18

Ahhh...
Claro, mi intencion era clonar el objeto para usarlo de almacenamiento temporal y al liberarlo me pasaba lo que ha explicado Nuño.

¿Y si quiero clonarlo , como se debería hacer? hay algo como Copy...?

PD: Algo hemos avanzado con los conceptos de objetos... Lo reconozco he usado algun Destroy por ahi :o

PD2: Muy buena explicacion.. concluyendo debo expresar mi nivel de Delphi
con una variable Double; var Nivel:Double; .. Nivel:=0.001;:D:D:D

Ñuño Martínez 08-04-2010 15:47:53

Cita:

Empezado por alquimista (Mensaje 359734)
Ahhh...
Claro, mi intencion era clonar el objeto para usarlo de almacenamiento temporal y al liberarlo me pasaba lo que ha explicado Nuño.

¿Y si quiero clonarlo , como se debería hacer? hay algo como Copy...?

Pues hasta donde yo sé, no hay un método para clonar objetos. Cada clase debe definir su propio método de copia. Si no tiene, se puede intentar asignando a través de sus propiedades.

Sirva como ejemplo la clase "TStrings". Puede clonarse un objeto de esta clase asignando la propiedad Text, es decir:

Código Delphi [-]
ObjetoStringList.Text := ObjetoOrigen.Text;

Ahora los dos objetos contienen una copia idéntica de las cadenas, pero cada uno es independiente. Sin embargo este sistema sólo sirve para esta clase y sus derivadas (por ejemplo, TStringList).

Lepe 08-04-2010 19:11:40

Ñuño, recuerda tambien:
Código Delphi [-]
var a, b:TStringList;
begin
  a := TStringlist.create;
  a.add('uno'); 
  a.add('dos');
  b:= TStringlist.create;
  b.Assign(a);

Para clonar objetos está el método Assign, pero pertenece a TPersistent y puede que incluya muchas cosas que no quieras. Además debes modificar el método Assign en tu clase para que sea 100 % operativo, total, como he soltado un rollo ahí vá lo que deberías hacer:
Código Delphi [-]
procedure TSecuencia.Assign(Avalue:TSecuencia);
begin
 Numero := Avalue.Numero
    Nombre  := Avalue..Nombre;
    Descripcion := Avalue.Descripcion;
   // o sea copiar a la instancia actual, la que se pasa por parámetro.
  // realmente no clona objetos, solo copia los valores de uno a otro

  Recorr.Origen := Avalue.Recorr.Origen
  SetLength(Recorr.tradmov, Avalue.Recorr.Count);
  for i:= 0 to Avalue.Recorr.Count -1 do // Count no debería ser privada ;)
     Recorr.tradmov[i] := Avalue.Recorr.tradmov[i]; 
  // hace años que no uso "records", delphi los copiaba ¿verdad?
  // sino un simple test y salimos de dudas.
Y ahora si podemos usar un código como...

Código Delphi [-]
var a, b: TSecuencia;
begin
  a := TSecuencia.create;
  b := TSecuencia.Create;
  a.Nombre := 'pepe';
  // relleanar todos los valores
  b.Assign(a);
  "b" será un clon de "a"

//queda por liberar y eso...

Esto que has visto es realmente lo que hace el método TPersistent.Assign(), pero es más lioso de entender por la herencia (la primera vez que vi el código de Assign no entendí nada de nada).

Edito:
como me quedé intrigado hice la prueba, Delphi si clona los registros con el operador ":="
Código Delphi [-]
type  tu = record
   nombre :string;
   end  ;

var   a,b:tu;
procedure TForm1.FormCreate(Sender: TObject);
begin
  a.nombre := 'pepe';
  b:= a;
  a.nombre := 'juan';
  ShowMessage('a.nombre es ' a.nombre + ' b.nombre es '+ b.nombre)
end;
obtenemos el mensaje "a.nombre es juan b.nombre es pepe"

alquimista 08-04-2010 23:13:22

Muchas gracias por las lecciones magistrales.

A ver si con toda esa ayuda puedo terminar mi programa..
Un saludo.

Una ultima pregunta:
¿Si libero ListaSprites, se libera la memoria de los objetos Secuencia que estan en ListaSprites?


Código:

for i:=1 TO MAXBOTS do
  if ListaSprites[i]<>nil then
      ListaSprites[i].free;

Código:

type
  TSprite = class
  public
    x, y, xAnterior, yAnterior: Integer;
    ColorTransparente: TColor;
    Imagen, Mascara,buffer: TImage;
    Seleccion: integer;
    deltx,delty:integer;
    Orientacion:TOrientacion;
    Secuencia: Array [1..MAXSEQ] of TSecuencia;

PD:A ver si puedo incrementar mi variable nivel un poco mas: nivel:=inc(nivel);:D:D:D:D:D:D

Ñuño Martínez 09-04-2010 09:33:42

Cita:

Empezado por Lepe (Mensaje 359770)
Edito:
como me quedé intrigado hice la prueba, Delphi si clona los registros con el operador ":="

Claro: porque los registros (RECORD) no se referencian como los objetos. Esa es una de las diferencias entre registros y clases.

Por cierto, gracias por la explicación acerca de "Assign". :)




Cita:

Empezado por alquimista (Mensaje 359818)
Una ultima pregunta:
¿Si libero ListaSprites, se libera la memoria de los objetos Secuencia que estan en ListaSprites?


Código Delphi [-]
for i:=1 TO MAXBOTS do
   if ListaSprites[i]<>nil then
      ListaSprites[i].free;

Exacto: tienes que liberar cada sprite por separado.

DriverOp 10-04-2010 00:56:05

Una regla que es bueno recordar siempre: lo que se crea por código, se debe destruir por código.

Lepe 10-04-2010 12:21:15

Cita:

Empezado por DriverOp (Mensaje 359871)
Una regla que es bueno recordar siempre: lo que se crea por código, se debe destruir por código.

Yo más bien diría: "Lo que delphi crea, delphi lo destruye, lo que creo yo, lo destruyo yo".

El tema viene por esto:
Código Delphi [-]
form2 := Application.CreateForm(TForm2, Form2);

// o bien

form2 := TForm2.Create(Application);

aquí estamos creando nosotros la ventana, pero le decimos que Delphi la destruya automáticamente al terminar la aplicación; nosotros no tenemos que destruirla.

Caso bien distinto a:
Código Delphi [-]
form2 := TForm2.Create(nil);
donde nosotros lo creamos y nadie se hará cargo de su destrucción. Nosotros debemos destruirla en el OnClose o llamar a form2.Free


La franja horaria es GMT +2. Ahora son las 01:43:50.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi