Olha que legal minha conversa com o ChatGPT sobre Polimorfismo no Javascript
Published: 2023-05-15 23:31:52


> **🙋🏻‍♀️ Eu: - Me dê um exemplo de polimorfismo usando Javascript.**

> 🤖 ChatGPT: - Claro! Aqui está um exemplo de polimorfismo em JavaScript:

```js
// Classe base
class Animal {
  constructor(name) {
    this.name = name;
  }

  makeSound() {
    console.log("O animal faz um som.");
  }
}

// Subclasse que estende a classe Animal
class Dog extends Animal {
  makeSound() {
    console.log("O cachorro late.");
  }
}

// Subclasse que estende a classe Animal
class Cat extends Animal {
  makeSound() {
    console.log("O gato mia.");
  }
}

// Função que recebe um objeto Animal e chama o método makeSound
function makeAnimalSound(animal) {
  animal.makeSound();
}

// Criando instâncias das classes Dog e Cat
const dog = new Dog("Rex");
const cat = new Cat("Whiskers");

// Chamando a função makeAnimalSound com diferentes objetos
makeAnimalSound(dog); // Saída: "O cachorro late."
makeAnimalSound(cat); // Saída: "O gato mia."
```

Nesse exemplo, temos uma classe base chamada `Animal` com um método `makeSound`. Em seguida, temos duas subclasses, `Dog` (cachorro) e `Cat` (gato), que estendem a classe base `Animal` e substituem o método `makeSound` para produzir o som específico de cada animal.

A função `makeAnimalSound` recebe um objeto do tipo `Animal` (que pode ser uma instância de Dog ou Cat) e chama o método `makeSound` do objeto. Dependendo do tipo do objeto passado, o método `makeSound` apropriado será executado.

Isso é **polimorfismo**, onde diferentes objetos podem responder de maneira diferente ao mesmo método, permitindo um código mais flexível e reutilizável.

---

> **🙋🏻‍♀️ Eu: - É possível aplicar polimorfismo na programaçao funcional?**

> 🤖 ChatGPT: - Na programação funcional, o conceito de **polimorfismo** é um pouco diferente do exemplo anterior, pois não há classes ou herança como na programação orientada a objetos.

No entanto, é possível obter um comportamento polimórfico usando **funções de alta ordem (higher-order functions)** e passando funções como parâmetros.

Aqui está uma adaptação do exemplo anterior para demonstrar o polimorfismo em programação funcional:

```js
// Função polimórfica que recebe uma função como argumento
function makeAnimalSound(animalFn) {
  return animalFn();
}

// Funções específicas para diferentes sons de animais
function dogSound() {
  console.log("O cachorro late.");
}

function catSound() {
  console.log("O gato mia.");
}

// Utilizando a função polimórfica com diferentes funções
makeAnimalSound(dogSound); // Saída: "O cachorro late."
makeAnimalSound(catSound); // Saída: "O gato mia."
```

Neste exemplo, temos a função `makeAnimalSound` que recebe uma **função como argumento** (animalFn). Essa função é polimórfica, pois pode receber diferentes funções específicas para diferentes sons de animais.

As funções `dogSound` e `catSound` são exemplos de funções específicas para diferentes sons de animais. Cada uma dessas funções produz um som específico quando é chamada.

Ao chamar a função `makeAnimalSound` com diferentes funções (dogSound e catSound), obtemos resultados diferentes, cada um correspondendo ao som específico do animal.

Assim, conseguimos obter um **comportamento polimórfico** ao passar diferentes funções para a função `makeAnimalSound` na programação funcional.

---

> **🙋🏻‍♀️ Eu: - Podemos dizer então, que a classe é polimorfica quando recebe diferentes classes como argumento?**

> 🤖 ChatGPT: - Sim, podemos dizer que uma classe é polimórfica quando ela recebe diferentes classes como argumento.

No contexto da programação orientada a objetos, o polimorfismo é alcançado por meio do conceito de **herança** e da capacidade de uma classe base tratar objetos de suas subclasses de maneira **uniforme**.

Quando uma **classe base** pode receber diferentes objetos de suas **subclasses** e chamar métodos com o mesmo nome, mas com implementações específicas em cada subclasse, dizemos que a **classe base é polimórfica**.

Vamos adaptar o exemplo do animal para ilustrar esse conceito:

```js
// Classe base
class Animal {
  constructor(name) {
    this.name = name;
  }

  makeSound() {
    console.log("O animal faz um som.");
  }
}

// Subclasse que estende a classe Animal
class Dog extends Animal {
  makeSound() {
    console.log("O cachorro late.");
  }
}

// Subclasse que estende a classe Animal
class Cat extends Animal {
  makeSound() {
    console.log("O gato mia.");
  }
}

// Função que recebe um objeto Animal e chama o método makeSound
function makeAnimalSound(animal) {
  animal.makeSound();
}

// Criando instâncias das classes Dog e Cat
const dog = new Dog("Rex");
const cat = new Cat("Whiskers");

// Chamando a função makeAnimalSound com diferentes objetos
makeAnimalSound(dog); // Saída: "O cachorro late."
makeAnimalSound(cat); // Saída: "O gato mia."
```

Neste exemplo, a classe `Animal` é **polimórfica**, pois pode receber diferentes objetos de suas subclasses `Dog` e `Cat`.

A função `makeAnimalSound` recebe um objeto do tipo `Animal` como argumento e chama o método `makeSound` do objeto.

Quando chamamos a função `makeAnimalSound` passando um objeto `dog`, o método `makeSound` da classe `Dog` é executado, produzindo o som específico do cachorro.

Da mesma forma, quando passamos um objeto `cat`, o método `makeSound` da classe `Cat` é executado, produzindo o som específico do gato.

Dessa forma, a classe `Animal` **é polimórfica** porque pode tratar diferentes objetos de suas subclasses de maneira uniforme e executar o **comportamento apropriado** para cada subclasse.

---

> **🙋🏻‍♀️ Eu: - Por favor, me dê um exemplo usando uma classe Product**

> 🤖 ChatGPT: - Claro! Aqui está um exemplo usando a classe Product (Produto) para ilustrar o polimorfismo:

```js
// Classe base
class Product {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }

  calculateDiscount() {
    return 0; // sem desconto por padrão
  }
}

// Subclasse que estende a classe Product
class SaleProduct extends Product {
  constructor(name, price, discountPercentage) {
    super(name, price);
    this.discountPercentage = discountPercentage;
  }

  calculateDiscount() {
    return (this.price * this.discountPercentage) / 100;
  }
}

// Subclasse que estende a classe Product
class ClearanceProduct extends Product {
  constructor(name, price, clearancePrice) {
    super(name, price);
    this.clearancePrice = clearancePrice;
  }

  calculateDiscount() {
    return this.price - this.clearancePrice;
  }
}

// Função que recebe um objeto Product e calcula o desconto
function calculateProductDiscount(product) {
  const discount = product.calculateDiscount();
  console.log(`O desconto do produto ${product.name} é de ${discount} reais.`);
}

// Criando instâncias das classes SaleProduct e ClearanceProduct
const saleProduct = new SaleProduct("Camiseta", 50, 20); // 20% de desconto
const clearanceProduct = new ClearanceProduct("Calça", 100, 70); // preço de liquidação de 70 reais

// Chamando a função calculateProductDiscount com diferentes objetos
calculateProductDiscount(saleProduct); // Saída: "O desconto do produto Camiseta é de 10 reais."
calculateProductDiscount(clearanceProduct); // Saída: "O desconto do produto Calça é de 30 reais."
```

Nesse exemplo, temos uma **classe base** chamada `Product` com um método `calculateDiscount`, que retorna o desconto do produto (ou zero, se não houver desconto).

Em seguida, temos duas **subclasses**, `SaleProduct` (Produto em promoção) e `ClearanceProduct` (Produto em liquidação), que estendem a classe base Product e **substituem o método** `calculateDiscount` para calcular o desconto de acordo com as regras específicas de cada subclasse.

A função `calculateProductDiscount` recebe um objeto do tipo `Product` (que pode ser uma instância de SaleProduct ou ClearanceProduct) e chama o método `calculateDiscount` do objeto para obter o valor do desconto.

Ao criar **instâncias** das subclasses `SaleProduct` e `ClearanceProduct` e chamar a função `calculateProductDiscount` com essas instâncias, obtemos resultados diferentes, dependendo do tipo do objeto.

O método `calculateDiscount` **apropriado** é executado para cada subclasse, fornecendo o valor de desconto correto para cada produto.

Dessa forma, o exemplo ilustra o polimorfismo na classe Product, onde diferentes objetos podem responder ao mesmo método de maneira diferente, permitindo um código mais **flexível** e **reutilizável**.

---

> **🙋🏻‍♀️ Eu: - Me dê o mesmo exemplo usando functions**

> 🤖 ChatGPT: - Claro! Aqui está um exemplo usando funções em vez de classes para ilustrar o polimorfismo:

```js
// Função base
function calculateDiscount(product) {
  return 0; // sem desconto por padrão
}

// Função para calcular o desconto de um produto em promoção
function calculateSaleDiscount(product) {
  return (product.price * product.discountPercentage) / 100;
}

// Função para calcular o desconto de um produto em liquidação
function calculateClearanceDiscount(product) {
  return product.price - product.clearancePrice;
}

// Objeto representando um produto em promoção
const saleProduct = {
  name: "Camiseta",
  price: 50,
  discountPercentage: 20, // 20% de desconto
};

// Objeto representando um produto em liquidação
const clearanceProduct = {
  name: "Calça",
  price: 100,
  clearancePrice: 70, // preço de liquidação de 70 reais
};

// Função que recebe um objeto produto e uma função de desconto e calcula o desconto
function calculateProductDiscount(product, discountFunction) {
  const discount = discountFunction(product);
  console.log(`O desconto do produto ${product.name} é de ${discount} reais.`);
}

// Chamando a função calculateProductDiscount com diferentes objetos e funções de desconto
calculateProductDiscount(saleProduct, calculateSaleDiscount); // Saída: "O desconto do produto Camiseta é de 10 reais."
calculateProductDiscount(clearanceProduct, calculateClearanceDiscount); // Saída: "O desconto do produto Calça é de 30 reais."
``` 

Nesse exemplo, temos funções separadas para calcular o desconto de diferentes tipos de produtos. A função `calculateDiscount` é a função base que retorna o desconto padrão de zero.

As funções `calculateSaleDiscount` e `calculateClearanceDiscount` são exemplos de funções específicas para calcular descontos de produtos em promoção e em liquidação, respectivamente.

Em vez de usar classes, utilizamos **objetos** para representar os produtos em promoção e em liquidação.

A função `calculateProductDiscount` recebe um `objeto de produto` e uma `função de desconto` **como argumentos**. Ela chama a `função de desconto` passando o objeto de produto e retorna o valor do desconto.

Ao chamar a função `calculateProductDiscount` com diferentes objetos de produto e funções de desconto correspondentes, obtemos resultados diferentes, cada um correspondendo ao desconto específico do tipo de produto.

Dessa forma, conseguimos obter um **comportamento polimórfico** ao passar diferentes objetos de produto e funções de desconto para a função `calculateProductDiscount`.

Cada função de desconto é executada para o tipo de produto apropriado, fornecendo o valor de desconto correto.

---

> **🙋🏻‍♀️ Eu: - Polimorfismo de uma function tem a ver com abstração de código?**

> 🤖 ChatGPT: - Sim, o polimorfismo em funções está relacionado à abstração de código. O polimorfismo permite tratar diferentes objetos ou tipos de dados de maneira uniforme, ocultando detalhes específicos de implementação e fornecendo uma **interface abstrata** para interagir com eles.

Ao utilizar o polimorfismo, podemos escrever **código genérico** que pode lidar com diferentes tipos de dados ou objetos, sem se preocupar com os detalhes específicos de cada um. Isso leva a um código mais **flexível**, **modular** e **reutilizável**.

Quando aplicamos o polimorfismo em funções, podemos passar diferentes objetos ou tipos de dados como argumentos para a mesma função, permitindo que a função opere de maneira polimórfica em cada um deles. A função pode tratar esses objetos ou tipos de dados de forma abstrata, chamando métodos comuns ou aplicando operações genéricas a eles.

Essa abstração de código oferecida pelo polimorfismo permite que você escreva funções genéricas que podem ser reutilizadas com diferentes objetos ou tipos de dados, promovendo uma melhor modularidade, flexibilidade e capacidade de extensão do código.

Portanto, o polimorfismo em funções desempenha um papel importante na abstração de código, permitindo que você escreva código mais genérico, modular e flexível, ao tratar diferentes objetos ou tipos de dados de forma uniforme.

---

> **🙋🏻‍♀️ Eu: - Por exemplo, em uma aplicação em React, recebemos produtos da api e precisamos converter seu preço (number) para o valor da moeda do país selecionado. Posso considerar essa function localeCurrency como uma function polimórfica?**

> 🤖 ChatGPT: - Sim, você pode considerar a função `localeCurrency` nesse cenário como uma função polimórfica. O polimorfismo ocorre quando uma função pode operar em diferentes tipos de dados ou objetos de maneira uniforme.

No seu exemplo em uma aplicação React, a função `localeCurrency` recebe um número (preço) e converte esse valor para a moeda específica com base no país selecionado.

Essa função pode ser aplicada a diferentes produtos e realizar a conversão da moeda corretamente, independentemente do tipo de produto ou do valor do preço.

A função `localeCurrency` é **polimórfica** porque ela pode ser usada com diferentes números (preços) e **fornecer a conversão adequada para cada um deles, independentemente do tipo específico do produto.**

Ao utilizar a função `localeCurrency` em diferentes contextos, você está tratando diferentes objetos (produtos) de maneira uniforme, aplicando a mesma lógica de conversão de moeda a cada um deles.

**Essa capacidade de tratar diferentes objetos de forma genérica e aplicar operações consistentes é uma característica fundamental do polimorfismo.**

Portanto, nesse caso, a função localeCurrency pode ser considerada uma função polimórfica, pois opera em diferentes valores numéricos (preços) de maneira uniforme, fornecendo a conversão correta da moeda com base na língua selecionada.

---