Clases en TypeScript: propiedades tipadas, modificadores de acceso y clases abstractas

Las clases en TypeScript son clases de JavaScript con un sistema de tipos encima. El resultado es una sintaxis más parecida a otros lenguajes orientados a objetos como Java o C#, pero compilando a JavaScript estándar. Esta guía cubre el tipado de propiedades, los modificadores de acceso, las clases abstractas y las diferencias entre el private de TypeScript y el #private nativo de JavaScript.

Propiedades tipadas y el shorthand de constructor

En TypeScript puedes declarar las propiedades de una clase fuera del constructor, lo que mejora la legibilidad y permite que TypeScript compruebe que todas se inicializan:

class Producto {
  id: number;
  nombre: string;
  precio: number;
  enStock: boolean = true; // valor por defecto

  constructor(id: number, nombre: string, precio: number) {
    this.id = id;
    this.nombre = nombre;
    this.precio = precio;
  }
}

El shorthand de constructor elimina la repetición declarando las propiedades directamente en los parámetros con un modificador de acceso:

class Producto {
  enStock: boolean = true;

  constructor(
    public id: number,
    public nombre: string,
    public precio: number
  ) {}
}

Modificadores de acceso: public, private y protected

Por defecto, todas las propiedades y métodos son public. private restringe el acceso al interior de la clase. protected permite acceso también desde subclases:

class CuentaBancaria {
  private saldo: number;
  protected titular: string;

  constructor(titular: string, saldoInicial: number) {
    this.titular = titular;
    this.saldo = saldoInicial;
  }

  depositar(cantidad: number): void {
    if (cantidad <= 0) throw new Error("Cantidad inválida");
    this.saldo += cantidad;
  }

  getSaldo(): number {
    return this.saldo;
  }
}

class CuentaPremium extends CuentaBancaria {
  mostrarResumen(): string {
    return `Titular: ${this.titular}`; // protected: accesible
    // this.saldo ? Error: propiedad privada de CuentaBancaria
  }
}

readonly

readonly impide que una propiedad se reasigne fuera del constructor. Es útil para valores que se deben inicializar una vez y no cambiar:

class Configuracion {
  readonly apiUrl: string;
  readonly timeout: number;

  constructor(apiUrl: string, timeout = 5000) {
    this.apiUrl = apiUrl;
    this.timeout = timeout;
  }
}

const config = new Configuracion("https://api.ejemplo.com");
config.apiUrl = "otro"; // Error: no se puede asignar a una propiedad readonly

Clases abstractas

Una clase abstracta define una interfaz que sus subclases deben implementar. No se puede instanciar directamente. Los métodos abstractos solo tienen declaración, no implementación:

abstract class Forma {
  abstract area(): number;
  abstract perimetro(): number;

  describir(): string {
    return `Área: ${this.area().toFixed(2)}, Perímetro: ${this.perimetro().toFixed(2)}`;
  }
}

class Circulo extends Forma {
  constructor(private radio: number) { super(); }

  area(): number { return Math.PI * this.radio ** 2; }
  perimetro(): number { return 2 * Math.PI * this.radio; }
}

class Rectangulo extends Forma {
  constructor(private ancho: number, private alto: number) { super(); }

  area(): number { return this.ancho * this.alto; }
  perimetro(): number { return 2 * (this.ancho + this.alto); }
}

implements: clases que cumplen interfaces

implements obliga a una clase a cumplir un contrato definido por una interfaz. No añade código en tiempo de ejecución, solo comprobación en tiempo de compilación:

interface Serializable {
  serializar(): string;
  deserializar(datos: string): void;
}

class Sesion implements Serializable {
  private datos: Record = {};

  serializar(): string {
    return JSON.stringify(this.datos);
  }

  deserializar(datos: string): void {
    this.datos = JSON.parse(datos);
  }
}

private de TypeScript vs #private de JavaScript

El private de TypeScript es una comprobación solo en tiempo de compilación: en el JavaScript resultante, la propiedad es accesible. El #private de JavaScript (soportado desde TypeScript 4.3) es un private real en tiempo de ejecución, gestionado por la especificación del lenguaje:

class Ejemplo {
  private tsPrivado = 1;  // accesible en JS si se fuerza
  #jsPrivado = 2;         // error en JS aunque se intente: sintaxis inaccesible

  obtenerJs(): number { return this.#jsPrivado; }
}

const e = new Ejemplo();
// (e as any).tsPrivado ? funciona en tiempo de ejecución
// (e as any).#jsPrivado ? SyntaxError en tiempo de ejecución

Para encapsulación real en tiempo de ejecución, usa #. Para encapsulación a nivel de tipado dentro de un equipo que controla todo el código TypeScript, private es suficiente y más legible.

Imagen: Pexels / Stanislav Kondratiev

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP