Cuando trabajamos con clases en TypeScript, a menudo nos encontramos con la necesidad de manejar atributos que no se almacenan directamente, sino que se derivan de otros datos. Estos son los llamados atributos virtuales, y para gestionarlos de forma elegante y eficiente, TypeScript nos ofrece los getters y setters.
Imaginemos que tenemos una clase Rectangulo con atributos como ancho y alto. Queremos obtener el área, que es un valor derivado de esos dos atributos. Una forma inicial sería definir un método que calcule el área, pero esto nos obliga a llamarlo como una función, con paréntesis, lo que puede resultar menos intuitivo. Otra opción es calcular el área en el constructor y almacenarla, pero esto tiene inconvenientes: cada vez que creamos una instancia, calculamos el área aunque no la necesitemos, y además, si modificamos ancho o alto después, el área almacenada puede quedar desactualizada.
Para resolver esto, podemos usar un getter. Un getter es una función que se define con la palabra clave get y que se comporta externamente como un atributo. Esto significa que podemos acceder a area como si fuera una propiedad, pero en realidad se ejecuta código para calcular su valor en el momento. Por ejemplo:
class Rectangulo {
ancho: number;
alto: number;
constructor(ancho: number, alto: number) {
this.ancho = ancho;
this.alto = alto;
}
get area(): number {
return this.ancho * this.alto;
}
}
const r1 = new Rectangulo(10, 5);
console.log(r1.area); // 50
Si añadimos un console.log dentro del getter, podemos ver que cada vez que accedemos a area, se ejecuta la función, lo que confirma que no es un atributo almacenado, sino derivado.
Además de los getters, existen los setters, que nos permiten definir qué ocurre cuando asignamos un valor a un atributo virtual. Por defecto, un getter sin setter se comporta como una propiedad de solo lectura, y si intentamos asignarle un valor, TypeScript nos dará un error. Pero si definimos un setter, podemos controlar la asignación y ejecutar código adicional, como validaciones, notificaciones o actualizaciones en bases de datos.
Para ilustrar esto, podemos crear un atributo virtual nombre en la clase Rectangulo. Internamente, almacenamos el valor en un atributo privado _nombre, y definimos un getter y un setter para nombre que gestionan el acceso y la modificación:
class Rectangulo {
ancho: number;
alto: number;
private _nombre: string = '';
constructor(ancho: number, alto: number) {
this.ancho = ancho;
this.alto = alto;
}
get area(): number {
return this.ancho * this.alto;
}
get nombre(): string {
console.log('Obtengo el nombre');
return this._nombre;
}
set nombre(value: string) {
console.log('Seteo el nombre');
this._nombre = value;
}
}
const r1 = new Rectangulo(10, 5);
r1.nombre = 'Rectángulo A'; // Se ejecuta el setter y muestra "Seteo el nombre"
console.log(r1.nombre); // Se ejecuta el getter y muestra "Obtengo el nombre" seguido del valor
Este patrón es muy útil para encapsular la lógica relacionada con la lectura y escritura de propiedades, permitiéndonos ejecutar código adicional cuando se accede o modifica un atributo, sin que el usuario de la clase tenga que preocuparse por ello. Por ejemplo, podríamos hacer que al cambiar el nombre se actualice una base de datos o se notifique a otros componentes del sistema.
En definitiva, los getters y setters nos permiten crear atributos virtuales que se comportan como propiedades normales desde el exterior, pero que internamente pueden ejecutar funciones complejas para derivar valores o controlar modificaciones. Esto mejora la encapsulación y la flexibilidad de nuestras clases en TypeScript.