Herança - JavaScript

Classes podem herdar de outras classes. A herança permite reduzir a quantidade de código nos classes derivadas. Por exemplo, considere as seguintes classes:

class Person {
    name;
    age;
    print() {
        console.log(`Name: ${this.name}  Age: ${this.age}`);
    }
}
class Employee {
    name;
    age;
    company;
    print() {
        console.log(`Name: ${this.name}  Age: ${this.age}`);
    }
    work() {
        console.log(`${this.name} works in ${this.company}`);
    }
}

const tom = new Person();
tom.name = "Tom";
tom.age = 34;
const bob = new Employee();
bob.name = "Bob";
bob.age = 36;
bob.company = "Google";
tom.print();    // Name: Tom  Age: 34
bob.print();    // Name: Bob  Age: 36
bob.work();     // Bob works in Google

Aqui, temos duas classes: Person, que representa uma pessoa, e Employee, que representa um funcionário de uma empresa. Ambas as classes funcionam bem, podemos criar objetos delas, mas também vemos que a classe Employee repete funcionalidades da classe Person, já que um funcionário também é uma pessoa com propriedades name e age e o método print.

A herança permite que uma classe obtenha automaticamente funcionalidades de outras classes, reduzindo assim a quantidade de código. Para herdar de uma classe, usamos a palavra-chave extends:

class Base {}
class Derived extends Base {}

Após o nome da classe derivada, colocamos a palavra-chave extends, seguida do nome da classe da qual queremos herdar funcionalidades.

Assim, modificamos as classes Person e Employee aplicando a herança:

class Person {
    name;
    age;
    print() {
        console.log(`Name: ${this.name}  Age: ${this.age}`);
    }
}
class Employee extends Person {
    company;
    work() {
        console.log(`${this.name} works in ${this.company}`);
    }
}

const tom = new Person();
tom.name = "Tom";
tom.age = 34;
const bob = new Employee();
bob.name = "Bob";
bob.age = 36;
bob.company = "Google";
tom.print();    // Name: Tom  Age: 34
bob.print();    // Name: Bob  Age: 36
bob.work();     // Bob works in Google

Agora, a classe Employee herda da classe Person. Neste contexto, a classe Person é também chamada de classe base ou classe pai, e Employee é uma classe derivada ou classe filha. Como a classe Employee herda funcionalidades de Person, não precisamos redefinir as propriedades name, age e o método print. Como resultado, o código da classe Employee fica mais curto, mas o resultado do programa é o mesmo.

Herança de Classe com Construtor

Junto com toda a funcionalidade, a classe derivada também herda o construtor da classe base. Por exemplo, definimos no classe base Person um construtor:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    print() {
        console.log(`Name: ${this.name}  Age: ${this.age}`);
    }
}
class Employee extends Person {
    company;
    work() {
        console.log(`${this.name} works in ${this.company}`);
    }
}

const tom = new Person("Tom", 34);
tom.print();    // Name: Tom  Age: 34

const sam = new Employee("Sam", 25);    // construtor herdado
sam.print();    // Name: Sam  Age: 25

Neste caso, a classe Person define um construtor com dois parâmetros. Neste caso, a classe Employee herda esse construtor e o utiliza para criar um objeto Employee.

Definindo um Construtor na Classe Derivada e a Palavra-Chave super

A classe derivada também pode definir seu próprio construtor. Se a classe derivada define um construtor, então o construtor da classe base deve ser chamado dentro dele. Para acessar a funcionalidade da classe base, incluindo o construtor, usamos a palavra-chave super:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    print() {
        console.log(`Name: ${this.name}  Age: ${this.age}`);
    }
}
class Employee extends Person {

    constructor(name, age, company) {
        super(name, age);
        this.company = company;
    }
    work() {
        console.log(`${this.name} works in ${this.company}`);
    }
}

const tom = new Person("Tom", 34);
tom.print();    // Name: Tom  Age: 34

const sam = new Employee("Sam", 25, "Google");
sam.print();    // Name: Sam  Age: 25
sam.work();     // Sam works in Google

A classe Employee define seu próprio construtor com três parâmetros, e a primeira linha desse construtor chama o construtor da classe base Person com super(name, age). Como o construtor da classe Person tem dois parâmetros, eles são passados adequadamente. Além disso, o construtor da classe base deve ser chamado antes de acessarmos as propriedades do objeto atual usando this.

Sobrescrevendo Métodos da Classe Base

Assim como com o construtor, a classe derivada pode sobrescrever métodos da classe base. Por exemplo, no exemplo acima, o método print() da classe Person exibe o nome e a idade da pessoa. Mas e se quisermos que para um empregado o método print() também mostre a empresa? Nesse caso, podemos definir nosso próprio método print() na classe Employee:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    print() {
        console.log(`Name: ${this.name}  Age: ${this.age}`);
    }
}
class Employee extends Person {

    constructor(name, age, company) {
        super(name, age);
        this.company = company;
    }
    print() {
        console.log(`Name: ${this.name}  Age: ${this.age}`);
        console.log(`Company: ${this.company}`);
    }
}
const sam = new Employee("Sam", 25, "Google");
sam.print();    // Name: Sam  Age: 25
                // Company: Google

No entanto, no código acima, a primeira linha do método print() na classe Employee basicamente repete o código do método print() da classe Person. Embora neste caso seja apenas uma linha, em outras situações o código repetido poderia ser mais extenso. Para evitar repetições, podemos simplesmente chamar a implementação do método print() da classe pai através de super:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    print() {
        console.log(`Name: ${this.name}  Age: ${this.age}`);
    }
}
class Employee extends Person {

    constructor(name, age, company) {
        super(name, age);
        this.company = company;
    }
    print() {
        super.print();
        console.log(`Company: ${this.company}`);
    }
}
const sam = new Employee("Sam", 25, "Google");
sam.print();    // Name: Sam  Age: 25
                // Company: Google

Assim, a chamada super.print(); representa a execução da implementação do método da classe base. Dessa forma, com this e super, podemos diferenciar o acesso às funcionalidades da classe atual ou de sua classe base.

Herança e Campos e Métodos Privados

Ao trabalhar com herança, é importante considerar que a classe derivada pode acessar qualquer funcionalidade da classe base, exceto campos e métodos privados. Por exemplo:

class Person {
    #name;
    constructor(name, age) {
        this.#name = name;
        this.age = age;
    }
    print() {
        console.log(`Name: ${this.#name}  Age: ${this.age}`);
    }
}
class Employee extends Person {
     
    constructor(name, age, company) {
        super(name, age);
        this.company = company;
    }
    print() {
        super.print();
        console.log(`Company: ${this.company}`);
    }
    work() {
        console.log(`${this.#name} works in ${this.company}`);  // Erro - campo #name não está acessível em Employee
    }
}

Neste caso, o campo #name na classe Person é definido como privado, portanto, está acessível apenas dentro dessa classe. Tentativas de acessar esse campo na classe derivada Employee resultarão em erro, independentemente de tentar acessá-lo através de this.#name ou super.#name. Se necessário, na classe base, podemos definir getters e setters que acessam campos privados, e a classe derivada pode então acessar esses campos privados por meio desses getters e setters.

Verificação da Pertinência de um Objeto a uma Classe

O fato de uma classe derivada ser herdada de uma classe base significa que um objeto da classe derivada também é um objeto da classe base. Podemos verificar a qual classe um objeto pertence usando o operador instanceof:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    print() {
        console.log(`Name: ${this.name}  Age: ${this.age}`);
    }
}
class Employee extends Person {
     
    constructor(name, age, company) {
        super(name, age);
        this.company = company;
    }
    print() {
        super.print();
        console.log(`Works in ${this.company}`);
    }
}
class Manager extends Person {
 
    constructor(name, age, company) {
        super(name, age);
        this.company = company;
    }
    print() {
        super.print();
        console.log(`Manager in ${this.company}`);
    }
}
const sam = new Employee("Sam", 25, "Google");
console.log(sam instanceof Person); // true
console.log(sam instanceof Employee); // true
console.log(sam instanceof Manager); // false

Aqui, a constante sam representa um objeto da classe Employee, que é derivada de Person. Portanto, as expressões sam instanceof Person e sam instanceof Employee retornarão true. Mas como sam não é um objeto da classe Manager, a expressão sam instanceof Manager retornará false.

Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com