anserran.dev

Clases e interfaces

Clases

En TypeScript puedes definir clases así:

class TaxPriceCalculator {
  private taxPercentage: number;

  constructor(taxPercentage: number) {
    this.taxPercentage = taxPercentage;
  }

  getTotalPrice(basePrice: number) {
    return basePrice + basePrice * this.taxPercentage;
  }
}

const priceCalculator = new TaxPriceCalculator(0.21);
console.log(priceCalculator.getTotalPrice(100));

Una clase puede tener atributos y métodos como miembros. Hay un miembro especial, constructor que es la función que se ejecuta cuando se crea un nuevo objeto de la clase a través del operador new .

Todos los miembros tienen un modificador de acceso. Cuando no se especifica toma el valor de public, lo que da acceso al miembro desde fuera de la clase. En el ejemplo, getTotalPrice no tiene modificador, lo que lo hace un miembro público y que podamos invocarlo desde el objeto priceCalculator.

Cuando utilizamos private, el miembro puede accederse desde dentro de la clase a través de la variable this, como sucede con taxPercentage, pero no desde fuera. Con el siguiente código obtenemos un error:

console.log(priceCalculator.taxPercentage);
// ^ taxPercentage solo accessible desde clase TaxPriceCalculator

Hay un tercer modificador protected que es solo relevante cuando tenemos herencia de clases, que vemos más abajo.

Interfaces y herencia

Definimos una interfaz así:

interface PriceCalculator {
  getTotalPrice(basePrice: number): number;
}

Una clase implementa una o varias interfaces con implements:

class TaxPriceCalculator implements PriceCalculator {
  /* ... */
}

Una interfaz puede extender una o varias interfaces con extends:

interface DiscountCalculator extends PriceCalculator {
  getDiscount(): number;
}

Una clase puede extender a otra e implementar interfaces:

class FiftyPercentPriceCalculator
  extends TaxPriceCalculator
  implements DiscountCalculator
{
  getTotalPrice(basePrice: number): number {
    return super.getTotalPrice(basePrice);
  }

  getDiscount(): number {
    return 0.5;
  }
}

Con la variable super tenemos acceso a los miembros public o protected de la clase extendida. Cuando un miembro es protected no puede ser accedido desde fuera de la jerarquía de clases, tal y como sucedía con private.

Interfaces como tipos para objetos

La aplicación de interfaces no está limitada solo a clases. También pueden utilizarse para definir objetos:

interface Product {
  name: string;
  price: number;
}

const book: Product = {
  name: "Libro",
  price: 15,
};

Las propiedades de los objetos pueden incluir también funciones:

interface Product {
  name: string;
  price: number;
  calculateTotalPrice: (taxPercentage: number) => number;
}

const book: Product = {
  name: "Libro",
  price: 15,
  calculateTotalPrice(taxPercentage) {
    return this.price + this.price * taxPercentage;
  },
};

Propiedades opcionales

Cuando no es obligatorio para un objeto tener definida una propiedad, podemos marcarlo con el modificador ?:

interface Product {
  name: string;
  price?: number;
}

const book: Product = {
  name: "Libro",
};

Aquí tenemos de nuevo una confusa relación con undefined, puesto que podría parecer que usar el modificador ? en price debería ser equivalente a usar number | undefined, pero no:

interface Product {
  name: string;
  price: number | undefined;
}

const book: Product = {
  // ^ Falta por definir la propiedad `price`
  name: "Libro",
};

undefined nos obliga a dar un valor explícito a price, aunque ese valor sea undefined, impidiendo que podamos omitir la propiedad por completo.

La razón es que existen dos estados diferentes que son representables: uno en el que la propiedad price podría no estar presente en absoluto en el objeto (?) y otro en el que la propiedad está presente pero su valor está indefinido.

Siguiente