Clases y Herencia

En este capítulo se explicará cómo se pueden crear clases y cómo se pueden heredar de otras clases.

Clases y objetos.

Una clase es un modelo que define un conjunto de atributos y métodos que tendrán los objetos que se creen a partir de ella.

class Persona {
    nombre: string;
    edad: number;

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

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

let persona1 = new Persona('Juan', 30); // Se crea un objeto de la clase Persona
let persona2 = new Persona('Ana', 25); // Se crea otro objeto de la clase Persona

persona1.saludar(); // Hola, soy Juan y tengo 30 años.
persona2.saludar(); // Hola, soy Ana y tengo 25 años.

En el ejemplo anterior se ha creado una clase Persona que tiene dos atributos (nombre y edad) y un método (saludar). A partir de esta clase se han creado dos objetos (persona1 y persona2) que tienen los mismos atributos y métodos.

Herencia

La herencia es un mecanismo que permite crear una nueva clase a partir de una clase existente. La nueva clase hereda los atributos y métodos de la clase existente y puede añadir nuevos atributos y métodos.

class Empleado extends Persona {
    salario: number;

    constructor(nombre: string, edad: number, salario: number) {
        super(nombre, edad);
        this.salario = salario;
    }

    trabajar() {
        console.log(`${this.nombre} está trabajando.`);
    }
}

let empleado1 = new Empleado('Pedro', 35, 2000);

empleado1.saludar(); // Hola, soy Pedro y tengo 35 años.
empleado1.trabajar(); // Pedro está trabajando.

En el ejemplo anterior se ha creado una clase Empleado que hereda de la clase Persona. La clase Empleado tiene un atributo adicional (salario) y un método adicional (trabajar).

Métodos estáticos

Los métodos estáticos son métodos que se pueden llamar sin necesidad de crear un objeto de la clase.

class Calculadora {
    static sumar(a: number, b: number) {
        return a + b;
    }

    static restar(a: number, b: number) {
        return a - b;
    }
}

console.log(Calculadora.sumar(5, 3)); // 8
console.log(Calculadora.restar(5, 3)); // 2

En el ejemplo anterior se ha creado una clase Calculadora con dos métodos estáticos (sumar y restar). Estos métodos se pueden llamar directamente a través de la clase sin necesidad de crear un objeto.

Modificadores de acceso (public, private, protected)

En este apartado se explicarán los modificadores de acceso que se pueden utilizar en TypeScript.

public

El modificador public indica que los atributos y métodos son accesibles desde cualquier parte del código.

class Coche {
    public marca: string;
    public modelo: string;

    constructor(marca: string, modelo: string) {
        this.marca = marca;
        this.modelo = modelo;
    }

    mostrar() {
        console.log(`Marca: ${this.marca}, Modelo: ${this.modelo}`);
    }
}

let coche = new Coche('Seat', 'Ibiza');
console.log(coche.marca); // Seat
console.log(coche.modelo); // Ibiza
coche.mostrar(); // Marca: Seat, Modelo: Ibiza

En el ejemplo anterior se ha creado una clase Coche con dos atributos (marca y modelo) y un método (mostrar). Los atributos y métodos son públicos, por lo que se pueden acceder desde cualquier parte del código.

private

El modificador private indica que los atributos y métodos son accesibles únicamente desde la propia clase.

class Coche {
    private marca: string;
    private modelo: string;

    constructor(marca: string, modelo: string) {
        this.marca = marca;
        this.modelo = modelo;
    }

    mostrar() {
        console.log(`Marca: ${this.marca}, Modelo: ${this.modelo}`);
    }
}

let coche = new Coche('Seat', 'Ibiza');
console.log(coche.marca); // Error
console.log(coche.modelo); // Error
coche.mostrar(); // Marca: Seat, Modelo: Ibiza

En el ejemplo anterior se ha creado una clase Coche con dos atributos (marca y modelo) y un método (mostrar). Los atributos y métodos son privados, por lo que no se pueden acceder desde fuera de la clase.

protected

El modificador protected indica que los atributos y métodos son accesibles desde la propia clase y desde las clases hijas.

class Vehiculo {
    protected marca: string;
    protected modelo: string;

    constructor(marca: string, modelo: string) {
        this.marca = marca;
        this.modelo = modelo;
    }

    mostrar() {
        console.log(`Marca: ${this.marca}, Modelo: ${this.modelo}`);
    }
}

class Coche extends Vehiculo {
    constructor(marca: string, modelo: string) {
        super(marca, modelo);
    }

    mostrar() {
        console.log(`Coche: Marca: ${this.marca}, Modelo: ${this.modelo}`);
    }
}

let coche = new Coche('Seat', 'Ibiza');
coche.mostrar(); // Coche: Marca: Seat, Modelo: Ibiza

En el ejemplo anterior se ha creado una clase Vehiculo con dos atributos (marca y modelo) y un método (mostrar). Los atributos y métodos son protegidos, por lo que se pueden acceder desde la propia clase y desde las clases hijas.

Getters y setters

Los getters y setters son métodos especiales que se utilizan para acceder y modificar los atributos de una clase.

class Persona {
    private _nombre: string;

    constructor(nombre: string) {
        this._nombre = nombre;
    }

    get nombre() {
        return this._nombre;
    }

    set nombre(nombre: string) {
        this._nombre = nombre;
    }
}

let persona = new Persona('Juan');
console.log(persona.nombre); // Juan
persona.nombre = 'Pedro';
console.log(persona.nombre); // Pedro

En el ejemplo anterior se ha creado una clase Persona con un atributo privado (**_nombre**) y un getter y un setter para acceder y modificar el atributo.

Herencia y polimorfismo

El polimorfismo es un concepto que permite que un objeto de una clase hija se comporte como un objeto de la clase padre.

class Vehiculo {
    protected marca: string;
    protected modelo: string;

    constructor(marca: string, modelo: string) {
        this.marca = marca;
        this.modelo = modelo;
    }

    mostrar() {
        console.log(`Marca: ${this.marca}, Modelo: ${this.modelo}`);
    }
}

class Coche extends Vehiculo {
    constructor(marca: string, modelo: string) {
        super(marca, modelo);
    }

    mostrar() {
        console.log(`Coche: Marca: ${this.marca}, Modelo: ${this.modelo}`);
    }
}

let vehiculo: Vehiculo = new Coche('Seat', 'Ibiza');
vehiculo.mostrar(); // Coche: Marca: Seat, Modelo: Ibiza

En el ejemplo anterior se ha creado una clase Vehiculo con un método mostrar y una clase Coche que hereda de la clase Vehiculo y sobrescribe el método mostrar. Se crea un objeto de la clase Coche y se asigna a una variable de tipo Vehiculo. Al llamar al método mostrar se ejecuta el método de la clase Coche.

Abstract classes

Las clases abstractas son clases que no se pueden instanciar directamente, sino que se utilizan como base para crear otras clases.

abstract class Figura {
    abstract area(): number;
}

class Circulo extends Figura {
    private radio: number;

    constructor(radio: number) {
        super();
        this.radio = radio;
    }

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

let circulo = new Circulo(5);
console.log(circulo.area()); // 78.53981633974483

En el ejemplo anterior se ha creado una clase abstracta Figura con un método abstracto area. Se ha creado una clase Circulo que hereda de la clase Figura y sobrescribe el método area. Al crear un objeto de la clase Circulo se puede llamar al método area.

Interfaces

Las interfaces son un mecanismo que permite definir la estructura que deben tener los objetos.

interface Figura {
    area(): number;
}

class Circulo implements Figura {
    private radio: number;

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

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

let circulo = new Circulo(5);
console.log(circulo.area()); // 78.53981633974483

En el ejemplo anterior se ha creado una interfaz Figura con un método area. Se ha creado una clase Circulo que implementa la interfaz Figura y define el método area. Al crear un objeto de la clase Circulo se puede llamar al método area.

Reto

Crear una clase Animal con los siguientes atributos y métodos:

  • Atributos:
    • nombre: string
    • edad: number
  • Métodos:
    • constructor(nombre: string, edad: number): constructor que recibe el nombre y la edad del animal.
    • emitirSonido(): método que muestra un mensaje con el sonido del animal.

Crear una clase Perro que herede de la clase Animal con los siguientes atributos y métodos:

  • Atributos:
    • raza: string
  • Métodos:
    • constructor(nombre: string, edad: number, raza: string): constructor que recibe el nombre, la edad y la raza del perro.
    • emitirSonido(): método que muestra un mensaje con el sonido del perro.

Crear un objeto de la clase Perro y llamar al método emitirSonido.

🔍 Pista
class Animal {
    nombre: string;
    edad: number;

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

    emitirSonido() {
        console.log('Sonido del animal');
    }
}

class Perro extends Animal {
    raza: string;

    constructor(nombre: string, edad: number, raza: string) {
        super(nombre, edad);
        this.raza = raza;
    }

    emitirSonido() {
        console.log('Guau guau');
    }
}

let perro = new Perro('Toby', 5, 'Labrador');
perro.emitirSonido(); // Guau guau

Conclusiones

En este capítulo se ha explicado cómo se pueden crear clases y cómo se pueden heredar de otras clases. También se han explicado los modificadores de acceso, los métodos estáticos, los getters y setters, las clases abstractas y las interfaces.