que-es-un-constructor-y-destructor-en-programacion

Qué son los constructores y destructores

Un Constructor es un tipo especial de método que se utiliza para inicializar las nuevas instancias de una clase.

Es decir, cuando creamos una instancia de una clase, el Constructor es un método que se ejecuta automáticamente durante la creación, generalmente para establecer el estado inicial del objeto.

programacion-constructor

El constructor, construyendo tus objetos

¿Para qué podemos querer ejecutar un método al crear un objeto? Pues, generalmente para

  • Inicializar las variables y el estado interno
  • Realizar ciertas validaciones
  • O para no hacer absolutamente nada 🤷

Por que sí, la muchas veces lo que querremos hacer es absolutamente nada. No todos los objetos necesitan que hagamos “algo” en su constructor. De hecho, la mayoría no lo necesitan.

Por último, la mayoría de lenguajes permiten definir múltiples constructores para una clase, cada uno con diferentes parámetros. A esto se llama sobrecarga de constructores.

Ejemplos de constructores en distintos lenguajes

Generalmente el Constructor es un método que:

  • Nombre: Generalmente, tiene el mismo nombre que la clase a la que pertenece.
  • Parámetros: Recibe los parámetros
  • Sin Tipo de Retorno: A diferencia de otros métodos, los constructores no tienen un tipo de retorno, ni siquiera void.

Veamos que pinta tiene un constructor en diferentes lenguajes de programación, y como lo usaríamos para crear instancias de una clase Persona.

public class Persona {
    public string nombre;
    public int edad;
    
    // Constructor
    public Persona(string nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    public void Presentarse() {
        Console.WriteLine($"Hola, mi nombre es {nombre} y tengo {edad} años.");
    }
}

// Crear una instancia de la clase Persona
Persona personaLuis = new Persona("Luis", 30);
personaLuis.Presentarse();

// Crear otra instancia de la clase Persona
Persona personaMaria = new Persona("Maria", 20);
personaMaria.Presentarse();
class Persona {
public:
    string nombre;
    int edad;

    // Constructor
    Persona(string nombre, int edad) {
        this->nombre = nombre;
        this->edad = edad;
    }

    void presentarse() {
        cout << "Hola, mi nombre es " << nombre << " y tengo " << edad << " años." << endl;
    }
};

// Crear una instancia de la clase Persona
Persona personaLuis("Luis", 30);
personaLuis.presentarse();

// Crear otra instancia de la clase Persona
Persona personaMaria("Maria", 20);
personaMaria.presentarse();
class Persona {
    constructor(nombre, edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    presentarse() {
        console.log(`Hola, mi nombre es ${this.nombre} y tengo ${this.edad} años.`);
    }
}

// Crear una instancia de la clase Persona
const personaLuis = new Persona("Luis", 30);
personaLuis.presentarse();

// Crear otra instancia de la clase Persona
const personaMaria = new Persona("Maria", 20);
personaMaria.presentarse();
class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def presentarse(self):
        print(f"Hola, mi nombre es {self.nombre} y tengo {self.edad} años.")

# Crear una instancia de la clase Persona
personaLuis = Persona("Luis", 30)
personaLuis.presentarse()

# Crear otra instancia de la clase Persona
personaMaria = Persona("Maria", 20)
personaMaria.presentarse()

En los ejemplos anteriores, vemos que el constructor de la clase Persona, e Inicializa las variables de instancia nombre y edad.

Por otro lado, vemos como se usa el constructor cuando creamos una instancia de clase para el caso de Luis con edad 30, y Maria con edad 20.

Destructores

Así como los constructores se encargan de la inicialización, los Destructores (o finalizadores) son métodos especiales que se ejecutan cuando un objeto es destruido o liberado.

Los Destructores se utilizan para realizar cualquier limpieza necesaria antes de que el objeto sea eliminado de la memoria. En general, principalmente para liberar recursos, como cerrar archivos o conexiones a bases de datos.

Ejemplos de Destructores en distintos lenguajes

Veamos cómo crear Destructores o finalizadores en distintos lenguajes de programación,

En C#, los destructores (~Persona) se llama automáticamente cuando el objeto se va a destruir, permitiendo la liberación de recursos.

public class Persona {
    public string nombre;
    public int edad;

    // Constructor
    public Persona(string nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    // Destructor
    ~Persona() {
        Console.WriteLine($"El objeto Persona {nombre} ha sido destruido.");
    }
}

De forma similar, en C++ el destructor (~Persona) también se declara de forma similar.

class Persona {
public:
    string nombre;
    int edad;

    // Constructor
    Persona(string nombre, int edad) {
        this->nombre = nombre;
        this->edad = edad;
    }

    // Destructor
    ~Persona() {
        cout << "El objeto Persona " << nombre << " ha sido destruido." << endl;
    }
};

En JavaScript, no existe un concepto de destructores. Sin embargo, podemos utilizar ciertos patrones para limpiar recursos si es necesario.

class Persona {
    constructor(nombre, edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    presentarse() {
        console.log(`Hola, mi nombre es ${this.nombre} y tengo ${this.edad} años.`);
    }

    // Método para liberar recursos manualmente
    liberarRecursos() {
        console.log(`Recursos de ${this.nombre} han sido liberados.`);
    }
}

// Ejemplo de uso
const personaLuis = new Persona("Luis", 30);
personaLuis.presentarse();
personaLuis.liberarRecursos();

En Python, el destructor (__del__) se llama cuando el objeto es recolectado por el garbage collector, o cuando forzamos su destrucción usando del.

class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def __del__(self):
        print(f"El objeto Persona {self.nombre} ha sido destruido.")

    def presentarse(self):
        print(f"Hola, mi nombre es {self.nombre} y tengo {self.edad} años.")

# Ejemplo de uso
personaLuis = Persona("Luis", 30)
personaLuis.presentarse()
del personaLuis  # Forzar la destrucción del objeto

Buenas prácticas Consejos

Cuando usar un constructor

A lo largo de tu vida como programador, probablemente pasarás por una relación amor-odio con los constructores. Sobre todo en cuanto a cuando tienes que usar un Constructor, y cuando es mejor usar otros mecanismos.

Probablemente al principio los usarás tímidamente. Luego estarás tentado de usarlos para todo. Y al final empezarás a no usarlos en absoluto, y preferir otras formas de inicializar objetos como el Patrón Factoría.

En cualquier caso, cuanto menos uses los constructores mejor (polémica 😮). Es preferible tener objetos simples, que contengan la menor lógica posible, y usarlos como contenedores de datos.

Para mi ¿Cuándo sí tiene sentido emplear un constructor? Cuando estés inicializando variables que el objeto necesita para funcionar y sin esa variable no tiene sentido ni que exista.

Por ejemplo, imaginemos que tienes un objeto Repository, cuya función es grabar datos en una base de datos. Para eso, necesita una conexión con una base de datos.

public class Repository
{
	DbConnection _connection;
	
	Repository(DbConnection connection)
	{
		_connection = connection;
	}

	// más metodos
}

No es debatible que Repository tenga una conexión. Sin eso, cualquier llamada a ese objeto va a resultar en un error. Literalmente, el objeto Repository no tiene sentido que exista sin una conexión.

En ese caso, es lógico que Repository reciba la conexión en el constructor. Porque es lo mínimo necesario para que funcione.

Pero, ¿y un objeto Coche? Podríamos pensar que lo mínimo que necesita un Coche, es su matricula

public class Coche
{
	string _matricula;
	
	Coche(string matricula)
	{
		_matricula = matricula;
	}
}

Pero, ¿y si puedo tener en un concesionario coches que aún no tienen matrícula? ¿Cogemos el número de bastidor? ¿Y si lo que estoy programando es un Parking, y no tengo números de bastidor?

Es decir, no hay una respuesta única. Lo que es necesario, y lo que debe ir como constructor, depende de tu modelo de objetos.

Eso forma parte del proceso de diseño del modelo de objetos de tu programa. Como consejo, no uséis un constructor salvo en aquellos casos que tengáis muy claro.

Buenas prácticas

Por otro lado, cuando necesitéis / decidáis usar un constructor, seguir siempre las siguientes pautas:

  • Lo más sencillos posibles
  • Nunca debe ser un proceso largo (como leer de una base de datos, de un fichero, etc)
  • Que sean lo más independientes posible del resto de clases

Cuando usar un destructor

Por su parte, los Destructores son menos abiertos a debates. Si durante el uso de una clase estáis empleando un recurso, debéis aseguraros de que se libera al destruir la instancia. Aquí no hay tanta duda de cuando usarlo, esto hay que hacerlo siempre.