37 - POO - Constructor y destructor de una clase

Un constructor es un método especial de una clase que tiene por objetivo inicializar campos de la clase (normalmente otros objetos)
El método Create heredado de la clase TObject es un constructor y en muchas situaciones lo reescribimos en las subclases. Puede haber otros constructores con otros nombres en la clase.

El destructor es otro método de la clase que será el último que se ejecute y tiene por objetivo liberar espacio de campos de la clase.

La clase TObject tiene un destructor llamado Destroy que es el que generalmente reescribimos en las subclases.

Tanto constructores y destructores de clase son opcionales, como ha sucedido en ejemplos anteriores donde no hemos definido estos tipos especiales de métodos.

Con un ejemplo veremos como plantear constructor y destructor de una clase.

Problema 1

Un banco tiene 3 clientes que pueden hacer depósitos y extracciones. También el banco requiere que al final del día calcule la cantidad de dinero que hay depositada.

Lo primero que hacemos es identificar las clases:
Podemos identificar la clase TCliente y la clase TBanco.

Luego debemos definir los campos y los métodos de cada clase:

TCliente		
    campos
        Nombre
        Monto
    métodos
        Depositar
        Extraer
        Imprimir

TBanco
    campos
        3 Cliente (3 objetos de la clase TCliente)
    métodos
        constructor
        Operar
        DepositosTotales
        destructor

Proyecto135

program Proyecto135;

{$APPTYPE CONSOLE}

type
  TCliente = class
    Nombre: String;
    Monto: Double;
    constructor Create(nom: String);
    procedure Depositar(mon: Double);
    procedure Extraer(mon: Double);
    procedure Imprimir;
  end;

  TBanco = class
    Cliente1: TCliente;
    Cliente2: TCliente;
    Cliente3: TCliente;
    constructor Create;
    destructor Destroy; Override;
    procedure Operar;
    procedure DepositosTotales;
  end;

constructor TCliente.Create(nom: string);
begin
  inherited Create;
  Nombre := nom;
  Monto := 0;
end;

procedure TCliente.Depositar(mon: Double);
begin
  Monto := Monto + mon
end;

procedure TCliente.Extraer(mon: Double);
begin
  Monto := Monto - mon;
end;

procedure TCliente.Imprimir;
begin
  WriteLn('El cliente ', Nombre, ' tiene depositado el monto de ', Monto:0:2);
end;

constructor TBanco.Create;
begin
  inherited Create;
  Cliente1 := TCliente.Create('Juan');
  Cliente2 := TCliente.Create('Ana');
  Cliente3 := TCliente.Create('Pedro');
end;

destructor TBanco.Destroy;
begin
  Cliente1.Free;
  Cliente2.Free;
  Cliente3.Free;
  inherited Destroy;
end;

procedure TBanco.Operar;
begin
  Cliente1.Depositar(100);
  Cliente2.Depositar(300);
  Cliente3.Depositar(400);
  Cliente1.Extraer(50);
end;

procedure TBanco.DepositosTotales;
var
  suma: Double;
begin
  suma := Cliente1.Monto + Cliente2.Monto + Cliente3.Monto;
  Cliente1.Imprimir;
  Cliente2.Imprimir;
  Cliente3.Imprimir;
  WriteLn('El banco tiene un total de ', suma:0:2);
end;


var
  Banco1: TBanco;
begin
  Banco1 := TBanco.Create;
  Banco1.Operar;
  Banco1.DepositosTotales;
  Banco1.Free;
  ReadLn;
end.

La clase TCliente define el constructor Create que recibe como parámetro un String con el nombre del cliente:

  TCliente = class
    Nombre: String;
    Monto: Double;
    constructor Create(nom: String);
    procedure Depositar(mon: Double);
    procedure Extraer(mon: Double);
    procedure Imprimir;
  end;

El algoritmo del constructor inicia el campo Nombre con el valor que llega en el parámetro y el campo Monto carga el valor cero:

constructor TCliente.Create(nom: string);
begin
  inherited Create;
  Nombre := nom;
  Monto := 0;
end;

El constructor como dijimos tiene por objetivo cargar algunos o todos los campos, en nuestro ejemplo cargamos Nombre y Monto.

La clase TBanco define 3 campos de tipo TCliente. Esto significa que en algún momento debemos crear esos tres objetos y en otro liberar el espacio ocupado por los mismos:

  TBanco = class
    Cliente1: TCliente;
    Cliente2: TCliente;
    Cliente3: TCliente;
    constructor Create;
    destructor Destroy; Override;
    procedure Operar;
    procedure DepositosTotales;
  end;

En el constructor Create procedemos a iniciar los tres campos:

constructor TBanco.Create;
begin
  inherited Create;
  Cliente1 := TCliente.Create('Juan');
  Cliente2 := TCliente.Create('Ana');
  Cliente3 := TCliente.Create('Pedro');
end;

Como vemos llamamos al constructor Create que creamos en la clase TCliente pasando como dato un String que representa el nombre del cliente.

El constructor Create de la clase TBanco será el primer método que se ejecutará, luego el método operar:

procedure TBanco.Operar;
begin
  Cliente1.Depositar(100);
  Cliente2.Depositar(300);
  Cliente3.Depositar(400);
  Cliente1.Extraer(50);
end;

Como los tres objetos Cliente1, Cliente2 y Cliente3 ya fueron creados en el constructor ya podemos llamar a sus métodos como son los de Depositar y Extraer:

procedure TBanco.Operar;
begin
  Cliente1.Depositar(100);
  Cliente2.Depositar(300);
  Cliente3.Depositar(400);
  Cliente1.Extraer(50);
end;

Lo mismo en el método DepositosTotales podemos imprimir lo que tiene cada cliente en su cuenta y sumar dichos montos:

procedure TBanco.DepositosTotales;
var
  suma: Double;
begin
  suma := Cliente1.Monto + Cliente2.Monto + Cliente3.Monto;
  Cliente1.Imprimir;
  Cliente2.Imprimir;
  Cliente3.Imprimir;
  WriteLn('El banco tiene un total de ', suma:0:2);
end;

Finalmente el destructor tiene por objetivo eliminar el espacio en memoria ocupado por los campos Cliente1, Cliente2 y Cliente3:

destructor TBanco.Destroy;
begin
  Cliente1.Free;
  Cliente2.Free;
  Cliente3.Free;
  inherited Destroy;
end;

Algo importante es que el destructor Destroy ya existe en la clase padre TObject y nosotros la debemos sobreescribir, para ello cuando la declaramos añadimos el modificador override:

    destructor Destroy; Override;

En el bloque principal solo definimos un objeto de la clase Banco (no definimos objetos de la clase Cliente ya que los clientes son campos definidos dentro de la clase TBanco):

var
  Banco1: TBanco;

En el bloque principal creamos el objeto Banco1 llamando al constructor Create que es el que creamos nosotros en la clase TBanco:

begin
  Banco1 := TBanco.Create;

Luego llamamos a los métodos Operar y DepositosTotales:

  Banco1.Operar;
  Banco1.DepositosTotales;

Finalmente debemos liberar el espacio ocupado por el objeto Banco1 llamando al método Free. El método Free se encarga de llamar al método Destroy que creamos nosotros en la clase TBanco:

  Banco1.Free;
  ReadLn;
end.

Problema 2

Plantear un programa que permita jugar a los dados. Las reglas de juego son: se tiran tres dados si los tres salen con el mismo valor mostrar un mensaje que "gano", sino "perdió".

Lo primero que hacemos es identificar las clases:

Podemos identificar la clase TDado y la clase TJuegoDeDados.

Luego los campos y los métodos de cada clase:

TDado		
    campo
        Valor
    métodos
        Tirar
        Imprimir

TJuegoDeDados
    campos
        3 TDado (3 objetos de la clase TDado)
    métodos
        constructor
        jugar
        destructor

Proyecto136

program Proyecto136;

{$APPTYPE CONSOLE}

type
  TDado = class
    Valor: Integer;
    procedure Tirar;
    procedure Imprimir;
  end;

  TJuegoDeDados = class
    Dado1: TDado;
    Dado2: TDado;
    Dado3: TDado;
    constructor Create;
    destructor Destroy; Override;
    procedure Jugar;
  end;

procedure TDado.Tirar;
begin
  Valor := Random(6) + 1;
end;

procedure TDado.Imprimir;
begin
  WriteLn('Valor del dado:', Valor);
end;

constructor TJuegoDeDados.Create;
begin
  inherited Create;
  Dado1 := TDado.Create;
  Dado2 := TDado.Create;
  Dado3 := TDado.Create;
end;

destructor TJuegoDeDados.Destroy;
begin
  Dado1.Free;
  Dado2.Free;
  Dado3.Free;
  inherited Destroy;
end;

procedure TJuegoDeDados.Jugar;
begin
  Dado1.Tirar;
  Dado1.Imprimir;
  Dado2.Tirar;
  Dado2.Imprimir;
  Dado3.Tirar;
  Dado3.Imprimir;
  if (Dado1.Valor = Dado2.Valor) and (Dado1.Valor = Dado3.Valor) then
    WriteLn('Ganó')
  else
    WriteLn('Perdió');
end;

var
  JuegoDeDados1: TJuegoDeDados;
begin
  Randomize;
  JuegoDeDados1 := TJuegoDeDados.Create;
  JuegoDeDados1.Jugar;
  JuegoDeDados1.Free;
  ReadLn;
end.

La clase TDado define el campo Valor y dos métodos:

  TDado = class
    Valor: Integer;
    procedure Tirar;
    procedure Imprimir;
  end;

El método Tirar genera un valor aleatorio comprendido entre 1 y 6:

procedure TDado.Tirar;
begin
  Valor := Random(6) + 1;
end;

El método Imprimir muestra el valor aleatorio generado en el otro método:

procedure TDado.Imprimir;
begin
  WriteLn('Valor del dado:', Valor);
end;

La clase TJuegoDeDados define tres campos de tipo TDado, esto hace que tengamos que declarar los métodos Create (donde creamos los tres dados) y el método Destroy (donde liberamos el espacio reservado para los tres objetos):

  TJuegoDeDados = class
    Dado1: TDado;
    Dado2: TDado;
    Dado3: TDado;
    constructor Create;
    destructor Destroy; Override;
    procedure Jugar;
  end;

En el constructor Create, primero llamamos al Create de la clase padre y luego creamos los tres objetos de la clase TDado:

constructor TJuegoDeDados.Create;
begin
  inherited Create;
  Dado1 := TDado.Create;
  Dado2 := TDado.Create;
  Dado3 := TDado.Create;
end;

En el destructor llamamos al Free de cada objeto y luego llamamos al Destroy de la clase padre:

destructor TJuegoDeDados.Destroy;
begin
  Dado1.Free;
  Dado2.Free;
  Dado3.Free;
  inherited Destroy;
end;

El método Jugar tira los tres dados y verifica si tienen el mismo valor:

procedure TJuegoDeDados.jugar;
begin
  Dado1.Tirar;
  Dado1.Imprimir;
  Dado2.Tirar;
  Dado2.Imprimir;
  Dado3.Tirar;
  Dado3.Imprimir;
  if (Dado1.Valor = Dado2.Valor) and (Dado1.Valor = Dado3.Valor) then
    WriteLn('Ganó')
  else
    WriteLn('Perdió');
end;

En le bloque principal de nuestro programa definimos un objeto de la clase TJuegoDeDados y llamamos a sus métodos:

var
  JuegoDeDados1: TJuegoDeDados;
begin
  Randomize;
  JuegoDeDados1 := TJuegoDeDados.Create;
  JuegoDeDados1.Jugar;
  JuegoDeDados1.Free;
  ReadLn;
end.

Problema propuesto

  • Plantear una clase TClub y otra clase TSocio.
    La clase TSocio debe tener los siguientes campos: nombre y la antigüedad en el club (en años). En el constructor pedir la carga del nombre y su antigüedad. La clase TClub debe tener como campos 3 objetos de la clase TSocio. Definir una responsabilidad para imprimir el nombre del socio con mayor antigüedad en el club.
Solución
program Proyecto137;

{$APPTYPE CONSOLE}

type
  TSocio = class
    Nombre: String;
    Antiguedad: Integer;
    constructor Create;
    procedure Imprimir;
  end;

  TClub = class
    Socio1: TSocio;
    Socio2: TSocio;
    Socio3: TSocio;
    constructor Create;
    destructor Destroy; override;
    procedure MasAntiguedad;
  end;

constructor TSocio.Create;
begin
  Write('Ingrese el nombre del socio:');
  ReadLn(Nombre);
  Write('Ingrese la antiguedad:');
  ReadLn(Antiguedad);
end;

procedure TSocio.Imprimir;
begin
  WriteLn('Socio ', Nombre, ' tiene una antiguedad de ', Antiguedad);
end;

constructor TClub.Create;
begin
  inherited Create;
  Socio1 := TSocio.Create;
  Socio2 := TSocio.Create;
  Socio3 := TSocio.Create;
end;

destructor TClub.Destroy;
begin
  Socio1.Free;
  Socio2.Free;
  Socio3.Free;
  inherited Destroy;
end;

procedure TClub.MasAntiguedad;
begin
  Socio1.Imprimir;
  Socio2.Imprimir;
  Socio3.Imprimir;
  WriteLn('Socio más antiguo en el club');
  if (Socio1.Antiguedad > Socio2.Antiguedad) and (Socio1.Antiguedad > Socio3.Antiguedad) then
    Socio1.Imprimir
  else
    if Socio2.Antiguedad > Socio3.Antiguedad then
      Socio2.Imprimir
    else
      Socio3.Imprimir;
end;

var
  Club1: TClub;
begin
  Club1 := TClub.Create;
  Club1.MasAntiguedad;
  Club1.Free;
  ReadLn;
end.