En esta nueva oportunidad hablaremos de los constructores y destructores. No, no es que existe una pandilla que construye código y otra que lo destruye.
Bueno, fueras broma, nos estamos refiriendo a los constructores y destructores que posee una clase.
Cuando uno desea crear un objeto, hace normalmente esto:
Objeto := TObjecto.Create;
Y con ello ya tiene en memoria una instancia de dicha clase.
Create es el constructor y se encarga de inicializar algunos o todos los atributos del objeto una vez que se ha reservado la memoria. Luego cuando ya no es necesario el objeto, simplemente se lo libera de memoria:
Objeto.Free;
El método Free envía el mensaje Destroy y éste último se encarga de liberar la memoria. Destroy es el destructor.
Un constructor y destructor no es más que otro método, con la salvedad en que en vez de definirlo como procedimiento o función se emplean las palabras "constructor" y "destructor" respectivamente. Por ejemplo:
type TPerro = class(TMamifero) ... public ... constructor Create; destructor Destroy; override; end;
Luego en su implementación se ve algo como esto:
constructor TPerro.Create; begin inherited Create; // algunas inicializaciones end; destructor TPerro.Destroy; begin // algunas operaciones de interés y liberaciones de objetos dependientes inherited Destroy; end;
Como puede apreciarse, a Destroy le he añadido la clásula override. Es fundamental declararla puesto que el constructor Destroy es virtual en TObject. En este ejemplo es redundante tanto el constructor como el destructor ya que el mismo constructor y Destructor de la clase TObject se encarga de la administración de memoria de los métodos simples.
Y además, se ve que intenamente se invoca al constructor y destructor de la clase TObject, al establecer la clásula inherited. Esto es necesario para que el constructor y destructor que definimos funcione apropiadamente. De éste modo nos garantizamos de que se inicializan los valores que se necesiten o se heredan desde TObject.
¿Cuando debemos declarar constructores y destructores?
Es necesario cuando entre los atributos, o internamente como alguna variable más, contamos con tipos complejos que requieren de manejo de memoria en forma explícita. Es decir cuando es nuestra responsabilidad manejar el uso de memoria. Por mencionar dos ejemplos: cuando un atributo es una clase o un array dinámico.
Por poner algo más visual. Digamos que queremos que nuestro TPerro tenga la habilidad de criar y mantener a sus crías (obviemos el tema semántico por el momento). Para tener referencia de sus crías podemos hacer uso de TObjectList. De este modo se concibe esta idea:
type TPerro = class private ListaCrias: TObjectList; ... public ... constructor Create; destructor Destroy override; end;
Como tenemos una clase definida, e internamente vamos a hacer uso de ella. Create es un método candidato (no necesariamente debe ser aquí donde se crea. Depende de diseño y propósitos) para crearla:
constructor TPerro.Create; begin inherited Create; ... FListaCrias := TObjectList.Create; ... end; destructor TPerro.Destroy; begin ... FListaCrrias.Clear; FListaCrias.Free; ... inherited Destroy; end;
Como puede apreciarse reservamos la memoria en Create y la liberamos en Destroy.
Continuaremos hablando del tema en otro post.