image

Acesse bootcamps ilimitados e +650 cursos

50
%OFF
Article image

E

Eduardo29/07/2024 18:40
Compartilhe
WEX - End to End EngineeringRecomendados para vocêWEX - End to End Engineering

A Sinfonia dos Objetos: Explorando a Programação Orientada a Objetos com JavaScript

  • #POO
  • #JavaScript

1. Introdução

Neste texto, exploraremos os conceitos de Programação Orientada a Objetos (POO) usando JavaScript, através da metáfora de uma sinfonia musical. Assim como uma sinfonia organiza diversos instrumentos para criar harmonia, a POO organiza objetos para criar programas que são eficazes, elegantes e harmoniosos. Cada objeto em um programa é como um instrumento na orquestra, contribuindo para uma execução coesa e sincronizada.

2. Conceito de Programação Orientada a Objetos

A Programação Orientada a Objetos (POO) é um paradigma que utiliza classes e objetos como elementos fundamentais para a criação de programas. Em JavaScript, as classes são como partituras que definem as propriedades (atributos) e as ações (métodos) que os objetos, ou "instrumentos", podem executar. Essa abordagem facilita a manutenção e o desenvolvimento de software, permitindo alterações sem impacto negativo no código existente, assim como na música, onde cada instrumento pode ser ajustado sem prejudicar a sinfonia como um todo.

Diferente do paradigma procedural, que foca em funções e procedimentos, a POO concentra-se em criar entidades que combinam dados e comportamento. Enquanto um programa procedural se assemelha a uma partitura linear, a POO é como uma orquestração onde cada objeto tem seu papel definido, mas pode interagir e colaborar com outros.

3. Classes e Objetos


3.1 Classe

Podemos pensar em uma classe como a partitura de um instrumento. Assim como uma partitura pode ser utilizada por diferentes músicos para tocar a mesma música, uma classe em JavaScript serve como um modelo para criar vários objetos. Estes objetos são as instâncias que seguem as diretrizes da classe, ou seja, a partitura que define como devem "soar" no contexto do programa.

class Violao {

constructor(timbre, marca, cor) {

  // Dados (Propriedades)

  this.timbre = timbre;

  this.marca = marca;

  this.cor = cor;

}

// Método (Ação)

mostrarPropriedades() {

  console.log(this.timbre, this.marca, this.cor);

}

}

3.2 Objeto

Cada objeto é como um instrumento único, com suas próprias características e capacidades. As propriedades do objeto representam características como timbre, marca e cor, enquanto os métodos representam as ações que ele pode executar, assim como as notas que um instrumento pode tocar. Em JavaScript, objetos são instâncias das classes, e podemos criar quantos objetos precisarmos para executar nossa "sinfonia" de código.

// Criando instâncias do objeto Violao

const violao1 = new Violao("Suave", "Gibson", "Preto");

violao1.mostrarPropriedades(); // Saída: Suave Gibson Preto

const violao2 = new Violao("Grave", "Fender", "Branco");

violao2.mostrarPropriedades(); // Saída: Grave Fender Branco

4. Pilares da Programação Orientada a Objetos

Para entender plenamente a POO, precisamos conhecer seus quatro pilares fundamentais: encapsulamento, herança, polimorfismo e abstração.

4.1. Abstração

A abstração em POO é semelhante a uma partitura que resume uma complexa composição musical em símbolos fáceis de seguir. Ela permite que desenvolvedores foquem apenas nos detalhes relevantes de um objeto, ocultando complexidades desnecessárias. Assim como um maestro não precisa saber cada detalhe de como um instrumento é construído para conduzir a orquestra, os desenvolvedores não precisam saber todos os detalhes internos de um objeto para usá-lo de forma eficaz.

4.2. Polimorfismo

Polimorfismo é como permitir que diferentes instrumentos toquem a mesma melodia, cada um com seu timbre único. Em POO, polimorfismo possibilita que objetos de diferentes classes sejam tratados como objetos de uma classe base comum, mas mantenham comportamentos específicos. Isso é particularmente útil em JavaScript, onde podemos definir métodos em uma classe base e permitir que subclasses os sobrescrevam, criando variações personalizadas sem modificar a estrutura principal.

// Classe base para instrumentos

class Instrumento {

tocarMelodia() {

  console.log("Tocando uma melodia...");

}

}

 

// Subclasse específica para o violino

class Violino extends Instrumento {

tocarMelodia() {

  console.log("O violino toca uma melodia suave.");

}

}

 

// Subclasse específica para a flauta

class Flauta extends Instrumento {

tocarMelodia() {

  console.log("A flauta toca uma melodia leve e fluida.");

}

}

 

// Função que aceita qualquer instrumento e toca a melodia

function apresentarInstrumento(instrumento) {

instrumento.tocarMelodia();

}

 

// Criando instâncias de diferentes instrumentos

const violino = new Violino();

const flauta = new Flauta();

 

// Usando a função para apresentar cada instrumento

apresentarInstrumento(violino); // Saída: O violino toca uma melodia suave.

apresentarInstrumento(flauta);  // Saída: A flauta toca uma melodia leve e fluida.

4.3. Herança

Herança em JavaScript difere de outras linguagens como Java e C++. Em JavaScript, utiliza-se a herança prototipal, onde objetos podem herdar diretamente de outros objetos. Com a introdução das classes em ES6, a sintaxe para herança tornou-se mais familiar para desenvolvedores vindos de linguagens baseadas em classes, mas ainda opera sob o paradigma prototipal.

A herança permite que subclasses recebam as propriedades e métodos de suas classes base, como se estivessem "herdando" características musicais de uma partitura mãe. Isso promove a reutilização de código, facilitando a manutenção e expansão de programas.

Exemplos de implementação de Herança Prototipal em JavaScript

Existem três principais maneiras de implementar herança prototipal em JavaScript:

1. Funções Construtoras

2. Classes ES6

3. Object.create()

4.3.1. Herança Prototipal com Funções Construtoras

As funções construtoras são usadas para criar objetos e definir métodos no protótipo para otimizar o uso da memória. Elas são uma maneira tradicional de criar objetos e implementar herança antes do ES6.

// Função construtora para criar músicos

function Musico(nome, instrumento) {

this.nome = nome;

this.instrumento = instrumento;

}

 

// Adicionando métodos ao protótipo do Musico

Musico.prototype.tocarMusica = function() {

console.log(`${this.nome} está tocando uma música com o instrumento: ${this.instrumento}`);

};
 
Musico.prototype.partitura = 'Sinfonia nº 9 de Beethoven';

Musico.prototype.mostrarPartitura = function() {

console.log(`${this.nome} está usando a partitura: ${this.partitura}`);

};

 

// Criando instâncias para diferentes músicos

const violinista = new Musico('Alice', 'violino');

const pianista = new Musico('Bob', 'piano');

 

// Usando os métodos

violinista.tocarMusica(); // Saída: Alice está tocando uma música com o instrumento: violino

violinista.mostrarPartitura(); // Saída: Alice está usando a partitura: Sinfonia nº 9 de Beethoven

 

pianista.tocarMusica(); // Saída: Bob está tocando uma música com o instrumento: piano

pianista.mostrarPartitura(); // Saída: Bob está usando a partitura: Sinfonia nº 9 de Beethoven


// Verificando protótipo

console.log(Musico.prototype); // Saída: Objeto com os métodos tocarMusica, partitura e mostrarPartitura

console.log(violinista.__proto__ === Musico.prototype); // Saída: true

Explicação: Criamos uma função construtora “Musico” e usamos Musico.prototype para adicionar os métodos, que serão acessíveis para todos os objetos criados com “Musico”

4.3.2. Herança Prototipal com Classes ES6

As classes ES6, que foram introduzidas no ES6 oferecem uma sintaxe mais familiar para definir objetos e herança, mas funcionam sobre a herança prototipal. 

// A classe Musico será a classe base, que representa um músico genérico e define métodos e  
// propriedades comuns que todos os músicos compartilham.

// Classe base para músicos

class Musico {

constructor(nome, instrumento) {

  this.nome = nome;

  this.instrumento = instrumento;

}


tocarMusica() {

  console.log(`${this.nome} está tocando uma música com o instrumento: ${this.instrumento}`);

}

mostrarPartitura() {

  console.log(`${this.nome} está usando a partitura: ${Musico.partitura}`);

}

}

// Adicionando uma propriedade estática à classe

Musico.partitura = 'Sinfonia nº 9 de Beethoven';

Subclasses para Diferentes Músicos

Agora, vamos criar subclasses específicas para diferentes tipos de músicos

// Subclasse específica para violinistas

class Violinista extends Musico {

constructor(nome) {

  super(nome, 'violino'); // Chama o construtor da classe base Musico

}

 

// Método adicional específico para violinistas

tocarSolo() {

  console.log(`${this.nome} está tocando um solo de violino!`);

}

}


// Subclasse específica para pianistas

class Pianista extends Musico {

constructor(nome) {

  super(nome, 'piano'); // Chama o construtor da classe base Musico

}

 

// Método adicional específico para pianistas

tocarSolo() {

  console.log(`${this.nome} está tocando um solo de piano!`);

}

}

Criando Instâncias de Músicos

// Criando instâncias de músicos específicos

const alice = new Violinista('Alice');

const bob = new Pianista('Bob');

 

// Chamando métodos da classe base e das subclasses

alice.tocarMusica();      // Saída: Alice está tocando uma música com o instrumento: violino

alice.mostrarPartitura(); // Saída: Alice está usando a partitura: Sinfonia nº 9 de Beethoven

alice.tocarSolo();        // Saída: Alice está tocando um solo de violino!

 

bob.tocarMusica();        // Saída: Bob está tocando uma música com o instrumento: piano

bob.mostrarPartitura();   // Saída: Bob está usando a partitura: Sinfonia nº 9 de Beethoven

bob.tocarSolo();          // Saída: Bob está tocando um solo de piano!

4.3.3. Herança Prototipal com Object.create()

A função Object.create() é uma maneira direta e flexível de criar objetos com um protótipo específico. Ela permite definir a herança de forma explícita.

Protótipo Base

// Protótipo base para músicos

const Musico = {

init(nome, instrumento) {

  this.nome = nome;

  this.instrumento = instrumento;

},

 

tocarMusica() {

  console.log(`${this.nome} está tocando uma música com o instrumento: ${this.instrumento}`);

},

 

mostrarPartitura() {

  console.log(`${this.nome} está usando a partitura: ${this.partitura}`);

},

 

partitura: 'Sinfonia nº 9 de Beethoven'

};

Sub-Objeto: Violinista e Pianista

// Sub-objeto específico para violinistas

const Violinista = Object.create(Musico);

Violinista.init = function(nome) {

Musico.init.call(this, nome, 'violino');

};

 

Violinista.tocarSolo = function() {

console.log(`${this.nome} está tocando um solo de violino!`);

};


// Sub-objeto específico para pianistas

const Pianista = Object.create(Musico);

Pianista.init = function(nome) {

Musico.init.call(this, nome, 'piano');

};

 
Pianista.tocarSolo = function() {

console.log(`${this.nome} está tocando um solo de piano!`);

};

Criando Instâncias de Músicos

// Criando instâncias de músicos específicos

const alice = Object.create(Violinista);

alice.init('Alice');

 

const bob = Object.create(Pianista);

bob.init('Bob');

 

// Usando os métodos das instâncias

alice.tocarMusica();      // Saída: Alice está tocando uma música com o instrumento: violino

alice.mostrarPartitura(); // Saída: Alice está usando a partitura: Sinfonia nº 9 de Beethoven

alice.tocarSolo();        // Saída: Alice está tocando um solo de violino!

 

bob.tocarMusica();        // Saída: Bob está tocando uma música com o instrumento: piano

bob.mostrarPartitura();   // Saída: Bob está usando a partitura: Sinfonia nº 9 de Beethoven

bob.tocarSolo();          // Saída: Bob está tocando um solo de piano!

Neste exemplo, foi utilizado o protótipo base "Musico", que define métodos comuns como "tocarMusica" e "mostrarPartitura", além de uma propriedade compartilhada chamada "partitura". Através de "Object.create()", criamos sub-objetos como "Violinista", "Pianista" e "Flautista", que herdam do protótipo base "Musico" e podem incluir métodos adicionais, como "tocarSolo". Cada sub-objeto redefine o método "init" para inicializar propriedades específicas, utilizando "Musico.init.call(this,nome, instrumento) para herdar as propriedades comuns. Instâncias de músicos específicos são então criadas usando "Object.create()", garantindo que compartilhem comportamento comum enquanto mantêm funcionalidades específicas.

4.4. Encapsulamento

Imagine cada seção de instrumentos de uma orquestra (cordas, instrumentos de sopro, metais, bateria) como um objeto em Programação Orientada a Objetos (POO). Cada seção possui partituras e ensaios específicos que são conhecidos apenas pelos músicos daquela seção. Esses elementos estão encapsulados dentro da seção, significando que apenas músicos autorizados podem acessar essas informações detalhadas. 


O encapsulamento em JavaScript permite controlar o acesso aos dados e métodos de um objeto, de forma semelhante a como controlamos o acesso a uma seção de instrumentos. Ele nos ajuda a proteger a integridade dos dados, permitindo a manipulação apenas através de interfaces definidas (métodos públicos), mantendo assim a coerência e segurança do sistema.

Propriedades Protegidas (_)

Propriedades protegidas são como partituras compartilhadas entre os músicos de uma seção e algumas seções relacionadas, como no caso da herança. Elas não devem ser alteradas por outros músicos ou seções que não tenham a responsabilidade direta por elas. Em JavaScript, usamos uma convenção de nomeação com um underscore (`_`) no início do nome da propriedade para indicar que ela é protegida. No entanto, isso é apenas uma convenção, sem força de restrição real; ou seja, ainda é possível acessar essas propriedades de fora da classe.

class Secao {

  constructor(nome, partituras) {

      this._nome = nome; // Propriedade protegida

      this._partituras = partituras; // Propriedade protegida

  }

 

  tocar() {

      console.log(`A seção de ${this._nome} está tocando: ${this._partituras}`);

  }

}

 

// Instanciando a seção de cordas

const cordas = new Secao("Cordas", "Sinfonia nº 9");

 

// Acessando método público

cordas.tocar(); // Saída: A seção de Cordas está tocando: Sinfonia nº 9

 

// Acessando propriedades protegidas (não recomendado, mas possível)

console.log(cordas._nome); // Saída: Cordas

console.log(cordas._partituras); // Saída: Sinfonia nº 9

Usamos (_) em this._nome e this._partitura para sinalizar que elas são uma propriedades protegidas. No entanto, ainda podemos acessar essas propriedades fora da classe, pois essa é apenas uma convenção criada por programadores, ou seja, quando sabemos que há (_) em uma propriedade, devemos evitar manipulá-la fora da classe para manter a integridade do objeto.

Propriedades Privadas (#)

Propriedades privadas são como partituras e ensaios estritamente confidenciais dentro de uma seção, que não podem ser acessados por ninguém fora dessa seção. Em JavaScript, para criar propriedades verdadeiramente privadas, utilizamos o símbolo `#` antes do nome da propriedade. Isso impede o acesso direto a essas propriedades de fora da classe, garantindo uma camada de proteção mais robusta.

class Secao {

  #nome; // Propriedade privada

  #partituras; // Propriedade privada

 

  constructor(nome, partituras) {

      this.#nome = nome;

      this.#partituras = partituras;

  }

 

  tocar() {

      console.log(`A seção de ${this.#nome} está tocando: ${this.#partituras}`);

  }

}

 

// Instanciando a seção de cordas

const cordas = new Secao("Cordas", "Sinfonia nº 9");

 

// Acessando método público

cordas.tocar(); // Saída: A seção de Cordas está tocando: Sinfonia nº 9

 

// Tentativa de acessar propriedades privadas (não permitido)

console.log(cordas.#nome); // SyntaxError: Private field '#nome' must be declared in an enclosing class

console.log(cordas.#partituras); // SyntaxError: Private field '#partituras' must be declared in an enclosing class

Para usar uma propriedade verdadeiramente privada em JavaScript, devemos usar # na frente da propriedade ou do método. Isso torna essas propriedades e métodos inacessíveis de fora da classe, protegendo-os completamente. Se precisarmos acessar propriedades privadas fora da classe, devemos criar métodos públicos que forneçam acesso indireto, mantendo a segurança e integridade do objeto.

Conclusão

Ao explorar os conceitos de POO com JavaScript, percebemos como a metáfora da sinfonia é adequada. Assim como maestros orquestram uma sinfonia harmoniosa a partir de diversos instrumentos, desenvolvedores utilizam a POO para criar software complexo e funcional a partir de objetos interconectados. JavaScript, com sua flexibilidade e abordagem única de herança, permite que cada "instrumento" de código contribua para uma execução coesa e impactante.


Referências

  1. Object-Oriented Programming in JavaScript
  2. O que é programação procedural e não procedural?
  3. Encapsulamento em JavaScript
  4. Object.create()
  5. Herança e cadeia de protótipos (prototype chain)
  6. O que são Objetos?
Compartilhe
Recomendados para você
TONNIE - Java and AI in Europe
WEX - End to End Engineering
Microsoft 50 Anos - Prompts Inteligentes
Comentários (0)
Recomendados para vocêWEX - End to End Engineering