TechLead
Lección 7 de 8
5 min de lectura
TypeScript

Clases en TypeScript

Usa clases con modificadores de acceso, clases abstractas e implementación de interfaces

Fundamentos de clases

// Clase básica
class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet(): string {
    return `Hello, I'm ${this.name}`;
  }
}

const person = new Person("Alice", 30);
console.log(person.greet());

// Atajo de propiedades en el constructor
class User {
  constructor(
    public name: string,
    public email: string,
    private password: string
  ) {}
}

// Equivalente a:
class UserVerbose {
  public name: string;
  public email: string;
  private password: string;

  constructor(name: string, email: string, password: string) {
    this.name = name;
    this.email = email;
    this.password = password;
  }
}

Modificadores de acceso

class BankAccount {
  // public - accesible en cualquier lugar (por defecto)
  public accountHolder: string;

  // private - solo accesible dentro de la clase
  private balance: number;

  // protected - accesible en la clase y subclases
  protected accountNumber: string;

  // readonly - solo se asigna en el constructor
  readonly createdAt: Date;

  constructor(holder: string, initialBalance: number) {
    this.accountHolder = holder;
    this.balance = initialBalance;
    this.accountNumber = this.generateAccountNumber();
    this.createdAt = new Date();
  }

  private generateAccountNumber(): string {
    return Math.random().toString(36).substring(2, 12);
  }

  public deposit(amount: number): void {
    if (amount > 0) {
      this.balance += amount;
    }
  }

  public getBalance(): number {
    return this.balance;
  }
}

const account = new BankAccount("Alice", 1000);
account.deposit(500);
console.log(account.getBalance());  // 1500
// account.balance;  // Error: private
// account.accountNumber;  // Error: protected

Herencia

class Animal {
  constructor(public name: string) {}

  move(distance: number = 0): void {
    console.log(`${this.name} moved ${distance} meters`);
  }

  speak(): void {
    console.log(`${this.name} makes a sound`);
  }
}

class Dog extends Animal {
  constructor(name: string, public breed: string) {
    super(name);  // Llamar al constructor padre
  }

  // Sobrescribir método del padre
  speak(): void {
    console.log(`${this.name} barks!`);
  }

  // Nuevo método
  fetch(): void {
    console.log(`${this.name} fetches the ball`);
  }
}

class Cat extends Animal {
  speak(): void {
    super.speak();  // Llamar método del padre
    console.log(`${this.name} meows!`);
  }
}

const dog = new Dog("Max", "Labrador");
dog.speak();   // "Max barks!"
dog.move(10);  // "Max moved 10 meters"
dog.fetch();   // "Max fetches the ball"

Clases abstractas

// Clase abstracta - no se puede instanciar directamente
abstract class Shape {
  constructor(public color: string) {}

  // Método abstracto - debe ser implementado por subclases
  abstract getArea(): number;
  abstract getPerimeter(): number;

  // Método concreto - implementación compartida
  describe(): string {
    return `A ${this.color} shape with area ${this.getArea()}`;
  }
}

class Circle extends Shape {
  constructor(color: string, public radius: number) {
    super(color);
  }

  getArea(): number {
    return Math.PI * this.radius ** 2;
  }

  getPerimeter(): number {
    return 2 * Math.PI * this.radius;
  }
}

class Rectangle extends Shape {
  constructor(
    color: string,
    public width: number,
    public height: number
  ) {
    super(color);
  }

  getArea(): number {
    return this.width * this.height;
  }

  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }
}

// const shape = new Shape("red");  // Error: no se puede instanciar una clase abstracta
const circle = new Circle("red", 5);
const rectangle = new Rectangle("blue", 4, 6);

console.log(circle.describe());
console.log(rectangle.getArea());

Implementar interfaces

interface Printable {
  print(): void;
}

interface Saveable {
  save(): Promise<void>;
  load(): Promise<void>;
}

// Implementar una interfaz
class Document implements Printable {
  constructor(public content: string) {}

  print(): void {
    console.log(this.content);
  }
}

// Implementar múltiples interfaces
class CloudDocument implements Printable, Saveable {
  constructor(public content: string, private id: string) {}

  print(): void {
    console.log(this.content);
  }

  async save(): Promise<void> {
    console.log(`Saving document ${this.id}`);
  }

  async load(): Promise<void> {
    console.log(`Loading document ${this.id}`);
  }
}

// Interface con restricción de clase
interface Repository<T> {
  findById(id: string): Promise<T | null>;
  save(item: T): Promise<void>;
  delete(id: string): Promise<void>;
}

class UserRepository implements Repository<User> {
  async findById(id: string): Promise<User | null> {
    return null;
  }

  async save(user: User): Promise<void> {}

  async delete(id: string): Promise<void> {}
}

Miembros estáticos

class MathUtils {
  // Propiedad estática
  static readonly PI = 3.14159;

  // Propiedad estática privada
  private static instance: MathUtils | null = null;

  // Método estático
  static add(a: number, b: number): number {
    return a + b;
  }

  static multiply(a: number, b: number): number {
    return a * b;
  }

  // Patrón singleton
  static getInstance(): MathUtils {
    if (!MathUtils.instance) {
      MathUtils.instance = new MathUtils();
    }
    return MathUtils.instance;
  }

  // Bloque estático (ES2022)
  static {
    console.log("MathUtils class initialized");
  }
}

// Uso - no necesitas instanciar
console.log(MathUtils.PI);
console.log(MathUtils.add(2, 3));
console.log(MathUtils.multiply(4, 5));

Getters y setters

class Temperature {
  private _celsius: number = 0;

  // Getter
  get celsius(): number {
    return this._celsius;
  }

  // Setter con validación
  set celsius(value: number) {
    if (value < -273.15) {
      throw new Error("Temperature below absolute zero!");
    }
    this._celsius = value;
  }

  // Propiedad calculada
  get fahrenheit(): number {
    return (this._celsius * 9) / 5 + 32;
  }

  set fahrenheit(value: number) {
    this._celsius = ((value - 32) * 5) / 9;
  }

  get kelvin(): number {
    return this._celsius + 273.15;
  }
}

const temp = new Temperature();
temp.celsius = 25;
console.log(temp.fahrenheit);  // 77
console.log(temp.kelvin);      // 298.15

temp.fahrenheit = 100;
console.log(temp.celsius);     // 37.78

Puntos clave

  • • Usa modificadores de acceso: public, private, protected, readonly
  • • Atajo de propiedades en parámetros del constructor
  • • Las clases abstractas definen contratos para subclases
  • • Las clases pueden implementar múltiples interfaces
  • • Los miembros estáticos pertenecen a la clase, no a instancias
  • • Getters/setters controlan el acceso a propiedades

Continuar Aprendiendo