Formação BackEnd

JavaScript

Módulo 3: JavaScript e Orientação a Objetos

Prof. Tiago Segato

Logo Bolsa Futuro Digital

Por que Programação Orientada a Objetos?

O "Muro da Complexidade"

Quando o código se torna difícil de gerenciar na programação procedural/estruturada:

  • Complexidade crescente: Funções e variáveis espalhadas sem organização clara
  • Dificuldade de reuso: Código repetitivo e difícil de reutilizar
  • Manutenção problemática: Mudanças em uma parte afetam outras inesperadamente
  • Escalabilidade limitada: Sistemas grandes se tornam ingerenciáveis

A Solução da POO

A ideia da POO é trazer o mundo real para o código, organizando-o em "objetos" autônomos que:

  • Agrupam dados e comportamentos relacionados
  • Facilitam a compreensão e manutenção
  • Promovem reutilização e modularidade
  • Permitem modelar sistemas complexos de forma intuitiva
"A POO nos permite pensar em termos de objetos do mundo real, tornando o código mais natural e organizado."

Introdução à Programação Orientada a Objetos

A Programação Orientada a Objetos (POO) é um paradigma que revolucionou o desenvolvimento de software:

  • O que é: Paradigma que utiliza "objetos" para modelar dados e comportamentos
  • JavaScript e POO: Suporte completo a partir do ES6 (2015) com sintaxe de classes
  • Vantagens:
    • Organização: Estrutura mais lógica e intuitiva
    • Reutilização: Reaproveitamento de código
    • Manutenção: Código mais fácil de manter
    • Escalabilidade: Sistemas complexos mais gerenciáveis

Pensando em Objetos

Como identificar objetos em um sistema:

  • Substantivos: Potenciais classes (Estudante, Professor, Disciplina)
  • Verbos: Potenciais métodos (matricular, atribuir, calcular)
"A POO nos permite modelar o mundo real em nosso código, criando sistemas mais intuitivos e organizados."

Os Quatro Pilares da Programação Orientada a Objetos

A Programação Orientada a Objetos se baseia em quatro pilares fundamentais:

Abstração

Focar no que é essencial. O que um Estudante faz e tem de importante para o nosso sistema? Ignoramos detalhes menos relevantes.

Encapsulamento

Proteger e agrupar dados e comportamentos. A "caixa-preta" que esconde detalhes internos e expõe apenas o necessário.

Herança

Reutilizar código. Um Professor e um Estudante são, antes de tudo, uma Pessoa. Permite criar hierarquias lógicas.

Polimorfismo

"Muitas formas". A capacidade de objetos diferentes responderem à mesma mensagem de maneiras diferentes.

"Estes quatro pilares são a base de qualquer sistema orientado a objetos bem projetado."

Objetos Literais vs Classes em JavaScript

Problema com Objetos Literais

// Exemplo ANTES de aprender classes - Código repetitivo! const estudante1 = { nome: "João Silva", matricula: "2023001", notas: [], calcularMedia: function() { if (this.notas.length === 0) return 0; const soma = this.notas.reduce((acc, nota) => acc + nota, 0); return soma / this.notas.length; } }; const estudante2 = { nome: "Maria Souza", matricula: "2023002", notas: [], calcularMedia: function() { // Código repetido! if (this.notas.length === 0) return 0; const soma = this.notas.reduce((acc, nota) => acc + nota, 0); return soma / this.notas.length; } }; // Problema: Como criar 100 estudantes sem repetir código?

Solução: Classes como "Fábrica" de Objetos

Classes são como uma "planta" ou "molde" para criar objetos similares, eliminando repetição de código.

Classes: A Planta dos Objetos

Sintaxe Básica de Classes (ES6+)

// A "planta" para todos os estudantes class Estudante { // O construtor é o primeiro método a ser executado constructor(nome, matricula, dataNascimento) { // 'this' se refere à instância atual do objeto sendo criado this.nome = nome; this.matricula = matricula; this.dataNascimento = dataNascimento; this.notas = []; // Um estudante começa sem notas } // Método para adicionar nota com validação adicionarNota(nota) { if (nota >= 0 && nota <= 10) { this.notas.push(nota); console.log(`Nota ${nota} adicionada para ${this.nome}.`); } else { console.log("Nota inválida. Deve ser entre 0 e 10."); } } // Método para calcular média calcularMedia() { if (this.notas.length === 0) return 0; const soma = this.notas.reduce((acc, nota) => acc + nota, 0); return (soma / this.notas.length).toFixed(2); } }

Criando Instâncias

// Criando objetos a partir da classe const joao = new Estudante("João Silva", "2023001", "2005-03-15"); const maria = new Estudante("Maria Souza", "2023002", "2006-07-20"); joao.adicionarNota(8.5); maria.adicionarNota(9.0); console.log(`Média do João: ${joao.calcularMedia()}`); // 8.50

Atributos e Métodos

Classes em JavaScript podem ter diferentes tipos de atributos e métodos:

Atributos de Instância

class Pessoa { constructor(nome, idade) { this.nome = nome; // Atributo público this.idade = idade; // Atributo público this._cpf = null; // Convenção para "privado" this.#senha = "1234"; // Realmente privado (ES2022+) } }

Métodos de Instância

class Pessoa { constructor(nome, idade) { this.nome = nome; this.idade = idade; } // Método público apresentar() { return `Olá, meu nome é ${this.nome}`; } // Método privado (ES2022+) #validarIdade() { return this.idade >= 18; } }

Atributos e Métodos Estáticos

Atributos e métodos estáticos pertencem à classe, não às instâncias:

Atributos Estáticos

class Matematica { static PI = 3.14159; // Atributo estático static calcularAreaCirculo(raio) { return Matematica.PI * raio * raio; } } console.log(Matematica.PI); // 3.14159 // Não precisa criar instância para usar!

Métodos Estáticos - Utilitários

class Utilitarios { static gerarIdAleatorio() { return Math.floor(Math.random() * 1000); } static formatarData(data) { return data.toLocaleDateString('pt-BR'); } } const id = Utilitarios.gerarIdAleatorio(); console.log(id); // Número aleatório entre 0 e 999

Quando usar: Para funcionalidades que fazem sentido para a classe como um todo, não para instâncias específicas.

Interação entre Objetos: Agregação e Composição

Objetos não vivem isolados. Eles interagem, colaboram e se relacionam:

Agregação - Relação "tem um"

Objetos têm ciclos de vida independentes:

  • Uma Disciplina tem um Professor
  • Se a disciplina for extinta, o professor continua existindo
  • Implementação: Um objeto guarda referência a outro

Composição - Relação "é parte de"

Ciclo de vida do objeto "parte" depende do objeto "todo":

  • Uma Nota é parte de uma Matrícula
  • Se a matrícula é cancelada, as notas perdem sentido
  • Relação mais forte que agregação
"Entender os relacionamentos entre objetos é fundamental para modelar sistemas complexos de forma eficiente."

Exemplo Prático: Sistema Escolar

class Disciplina { constructor(nome, codigo, cargaHoraria) { this.nome = nome; this.codigo = codigo; this.cargaHoraria = cargaHoraria; this.professor = null; // Agregação this.estudantesMatriculados = []; // Agregação } // Método para associar um Professor atribuirProfessor(professor) { if (professor instanceof Professor) { this.professor = professor; console.log(`Professor ${professor.nome} atribuído à ${this.nome}.`); } else { console.log("Erro: Objeto não é uma instância de Professor."); } } // Método para matricular um Estudante matricularEstudante(estudante) { if (estudante instanceof Estudante) { this.estudantesMatriculados.push(estudante); console.log(`${estudante.nome} matriculado em ${this.nome}.`); } else { console.log("Erro: Objeto não é uma instância de Estudante."); } } listarEstudantes() { console.log(`\n--- Estudantes em ${this.nome} ---`); this.estudantesMatriculados.forEach(estudante => { console.log(`- ${estudante.nome} (${estudante.matricula})`); }); } }

Encapsulamento: Protegendo seus Dados

O Princípio do Encapsulamento

  • Agrupar dados e métodos que operam nesses dados
  • Esconder detalhes internos de implementação
  • Proteger a integridade dos dados
  • Controlar o acesso através de métodos públicos

Campos Privados em JavaScript (ES2022+)

class Estudante { // Declaração de campos privados no topo #nome; #matricula; #notas; constructor(nome, matricula) { this.#nome = nome; this.#matricula = matricula; this.#notas = []; } // Getter para acesso controlado get nome() { return this.#nome; } // Getter que retorna cópia para evitar modificação externa get notas() { return [...this.#notas]; // Retorna cópia do array } // Método público que manipula dados privados adicionarNota(nota) { if (nota >= 0 && nota <= 10) { this.#notas.push(nota); } else { console.log(`Erro: Nota ${nota} é inválida.`); } } }

Getters e Setters

Getters e Setters permitem controlar o acesso aos atributos:

Sintaxe e Uso

class Produto { #preco; #nome; constructor(nome, preco) { this.#nome = nome; this.preco = preco; // Usa o setter } // Getter - permite leitura get nome() { return this.#nome; } get preco() { return this.#preco; } // Setter - permite escrita com validação set preco(valor) { if (valor > 0) { this.#preco = valor; } else { throw new Error("Preço deve ser positivo"); } } // Getter calculado get precoComDesconto() { return this.#preco * 0.9; // 10% de desconto } } const produto = new Produto("Notebook", 2000); console.log(produto.preco); // 2000 produto.preco = 1800; // Usa o setter console.log(produto.precoComDesconto); // 1620

Herança: Reutilizando Código

O Princípio da Herança

  • Relação "é um": Estudante é uma Pessoa
  • Reutilização: Subclasse herda atributos e métodos da superclasse
  • Hierarquias: Criação de estruturas lógicas e organizadas
  • Especialização: Subclasses podem adicionar comportamentos específicos

Sintaxe com extends e super()

// Classe Base (Superclasse) class Pessoa { constructor(nome, dataNascimento, cpf) { this.nome = nome; this.dataNascimento = dataNascimento; this.cpf = cpf; } apresentar() { return `Olá, meu nome é ${this.nome}`; } calcularIdade() { const hoje = new Date(); const nascimento = new Date(this.dataNascimento); return hoje.getFullYear() - nascimento.getFullYear(); } } // Subclasse que herda de Pessoa class Estudante extends Pessoa { constructor(nome, dataNascimento, cpf, matricula) { super(nome, dataNascimento, cpf); // Chama construtor da superclasse this.matricula = matricula; this.notas = []; } // Método específico da subclasse adicionarNota(nota) { this.notas.push(nota); } // Sobrescrita de método (Override) apresentar() { return `${super.apresentar()}, matrícula ${this.matricula}`; } }

Polimorfismo: Muitas Formas

Polimorfismo permite que objetos de classes diferentes respondam à mesma mensagem de maneiras diferentes:

class Professor extends Pessoa { constructor(nome, dataNascimento, cpf, idFuncional) { super(nome, dataNascimento, cpf); this.idFuncional = idFuncional; this.disciplinas = []; } // Implementação específica do método apresentar apresentar() { return `${super.apresentar()}, professor ID ${this.idFuncional}`; } } // Polimorfismo em ação const pessoas = [ new Pessoa("Ana", "1980-05-15", "123.456.789-00"), new Estudante("João", "2005-03-10", "987.654.321-00", "2023001"), new Professor("Carlos", "1975-12-20", "456.789.123-00", "PROF001") ]; // Mesmo método, comportamentos diferentes pessoas.forEach(pessoa => { console.log(pessoa.apresentar()); }); // Saída: // Olá, meu nome é Ana // Olá, meu nome é João, matrícula 2023001 // Olá, meu nome é Carlos, professor ID PROF001
"O polimorfismo permite escrever código mais flexível e extensível, tratando objetos diferentes de forma uniforme."

Sistema Escolar: Integrando os Conceitos

// Criando instâncias e conectando o sistema const profCarlos = new Professor("Carlos Andrade", "1975-08-15", "123.456.789-00", "PROF001"); const profAna = new Professor("Ana Dias", "1980-03-22", "987.654.321-00", "PROF002"); const joao = new Estudante("João Silva", "2005-03-15", "456.789.123-00", "2023001"); const maria = new Estudante("Maria Souza", "2006-07-20", "789.123.456-00", "2023002"); const matematica = new Disciplina("Matemática Avançada", "MAT101", 90); const historia = new Disciplina("História do Brasil", "HIS202", 60); // Realizando as associações matematica.atribuirProfessor(profCarlos); historia.atribuirProfessor(profAna); matematica.matricularEstudante(joao); matematica.matricularEstudante(maria); historia.matricularEstudante(maria); // Adicionando notas joao.adicionarNota(8.5); joao.adicionarNota(9.0); maria.adicionarNota(9.5); maria.adicionarNota(8.8); // Verificando o sistema console.log(matematica.professor.apresentar()); matematica.listarEstudantes(); console.log(`Média do João: ${joao.calcularMedia()}`); console.log(`Idade da Maria: ${maria.calcularIdade()} anos`);

Boas Práticas em POO com JavaScript

Nomenclatura e Organização

  • Classes: PascalCase (MinhaClasse)
  • Métodos e atributos: camelCase (meuMetodo)
  • Constantes: UPPER_SNAKE_CASE (MAX_TENTATIVAS)
  • Privados: Use # para campos realmente privados

Design e Arquitetura

  • Single Responsibility: Cada classe deve ter uma única responsabilidade
  • Encapsulamento: Mantenha dados privados e exponha apenas o necessário
  • Validação: Sempre valide dados nos setters e métodos
  • Documentação: Use comentários para explicar a lógica complexa

Verificação de Tipos

// Boa prática: verificar tipos antes de operações atribuirProfessor(professor) { if (!(professor instanceof Professor)) { throw new Error("Objeto deve ser uma instância de Professor"); } this.professor = professor; }
"Código limpo e bem estruturado é mais importante que código inteligente e complexo."

Conclusão: O Poder da POO

O que Aprendemos

  • Paradigma POO: Organização do código em objetos autônomos
  • Quatro Pilares: Abstração, Encapsulamento, Herança e Polimorfismo
  • Classes em JavaScript: Sintaxe moderna e recursos avançados
  • Relacionamentos: Como objetos interagem e colaboram
  • Boas Práticas: Código limpo, seguro e manutenível

Próximos Passos

  • Praticar com projetos reais
  • Explorar padrões de design (Design Patterns)
  • Aprender sobre frameworks que usam POO
  • Estudar arquiteturas orientadas a objetos

Benefícios da POO

  • Código mais organizado
  • Maior reutilização
  • Manutenção facilitada
  • Sistemas escaláveis

Aplicações Práticas

  • Sistemas web complexos
  • APIs e backends
  • Jogos e simulações
  • Aplicações empresariais
"A POO não é apenas uma técnica de programação, é uma forma de pensar e modelar soluções para problemas complexos."