Programação orientada a objetos: revisão de conceitos

1. Fundamentos da Orientação a Objetos

A Programação Orientada a Objetos (POO) é um paradigma de desenvolvimento que organiza o código em torno de "objetos" — entidades que combinam dados (atributos) e comportamentos (métodos). Diferentemente da programação procedural, que se baseia em funções e sequências de instruções, a POO busca modelar sistemas computacionais de forma mais próxima ao mundo real.

Classes e Objetos

Uma classe é um molde ou modelo que define a estrutura e o comportamento de um tipo de objeto. Um objeto é uma instância concreta de uma classe. Vejamos um exemplo básico:

class Carro:
    atributo modelo: string
    atributo ano: inteiro
    atributo ligado: booleano

    metodo ligar():
        ligado = verdadeiro

    metodo acelerar():
        se ligado == verdadeiro:
            imprimir("Acelerando...")

objeto meuCarro = new Carro()
meuCarro.modelo = "Fusca"
meuCarro.ano = 1978
meuCarro.ligar()
meuCarro.acelerar()

Abstração e Encapsulamento

Abstração é o processo de esconder detalhes complexos de implementação e expor apenas o essencial. Encapsulamento controla o acesso aos atributos e métodos de uma classe, utilizando modificadores como público, privado e protegido:

class ContaBancaria:
    privado saldo: decimal

    publico metodo depositar(valor: decimal):
        se valor > 0:
            saldo = saldo + valor

    publico metodo obterSaldo():
        retornar saldo

Aqui, o atributo saldo é privado, só podendo ser alterado pelos métodos públicos da classe, garantindo que regras de negócio sejam respeitadas.

2. Pilares da POO — Herança e Polimorfismo

Herança

A herança permite que uma classe filha reutilize atributos e métodos de uma classe pai, estabelecendo uma hierarquia:

class Animal:
    metodo emitirSom():
        imprimir("Som genérico")

class Cachorro herda Animal:
    metodo emitirSom():
        imprimir("Latido")

class Gato herda Animal:
    metodo emitirSom():
        imprimir("Miau")

Polimorfismo

O polimorfismo permite que um mesmo método tenha comportamentos diferentes em classes distintas. Existem dois tipos principais:

  • Sobrescrita (override): a classe filha redefine um método da classe pai, como no exemplo acima.
  • Sobrecarga (overload): múltiplos métodos com o mesmo nome, mas parâmetros diferentes.

Interfaces e Classes Abstratas

Classes abstratas não podem ser instanciadas diretamente e servem como base para outras classes. Interfaces definem contratos que as classes devem implementar:

interface Voador:
    metodo voar()

class Aviao implementa Voador:
    metodo voar():
        imprimir("Avião voando")

class Passaro implementa Voador:
    metodo voar():
        imprimir("Pássaro voando")

3. Associação, Agregação e Composição

Esses conceitos descrevem como objetos se relacionam entre si.

Associação Simples

Relacionamento onde objetos se conhecem, mas têm ciclos de vida independentes:

class Professor:
    atributo nome: string

class Aluno:
    atributo nome: string
    atributo professor: Professor

Agregação

Relação "tem-um" onde o objeto parte pode existir sem o todo:

class Departamento:
    atributo funcionarios: lista de Funcionario

class Funcionario:
    atributo nome: string

Um funcionário pode existir mesmo que o departamento seja extinto.

Composição

Relação "parte-de" onde a parte depende do ciclo de vida do todo:

class Casa:
    atributo comodos: lista de Comodo

class Comodo:
    atributo nome: string

Se a casa for destruída, os cômodos também deixam de existir.

4. Princípios SOLID na Prática

Os princípios SOLID são fundamentais para escrever código orientado a objetos de qualidade.

SRP — Responsabilidade Única

Uma classe deve ter apenas um motivo para mudar:

class Relatorio:
    metodo gerarRelatorio(): string
    metodo enviarEmail(): void  // violação: responsabilidade dupla

Correção: separar em Relatorio e EmailService.

OCP — Aberto/Fechado

Classes devem estar abertas para extensão, mas fechadas para modificação:

class Desconto:
    metodo calcular(valor): decimal

class DescontoClienteVIP herda Desconto:
    metodo calcular(valor): decimal
        retornar valor * 0.8

LSP — Substituição de Liskov

Subclasses devem ser substituíveis por suas classes base sem alterar o comportamento esperado:

class Retangulo:
    metodo definirLargura(largura)
    metodo definirAltura(altura)

class Quadrado herda Retangulo:  // viola LSP
    metodo definirLargura(largura):
        largura = altura = largura

5. Injeção de Dependência e Baixo Acoplamento

A injeção de dependência é uma técnica que implementa o princípio de inversão de controle (IoC), onde as dependências são fornecidas externamente em vez de criadas internamente.

Injeção via Construtor

class PedidoService:
    privado repositorio: PedidoRepository

    metodo PedidoService(repositorio: PedidoRepository):
        this.repositorio = repositorio

    metodo processarPedido(pedido):
        repositorio.salvar(pedido)

Vantagens

  • Facilita testes unitários (é possível injetar mocks)
  • Reduz acoplamento entre módulos
  • Torna o código mais flexível e reutilizável

6. Padrões de Projeto Clássicos em POO

Padrões de projeto são soluções reutilizáveis para problemas recorrentes.

Criacionais

  • Singleton: garante uma única instância de uma classe
  • Factory Method: delega a criação de objetos para subclasses

Estruturais

  • Adapter: permite que interfaces incompatíveis trabalhem juntas
  • Decorator: adiciona comportamento a objetos dinamicamente

Comportamentais

  • Strategy: permite que algoritmos sejam intercambiáveis
  • Observer: define uma dependência um-para-muitos entre objetos

Exemplo de Strategy:

interface PagamentoStrategy:
    metodo pagar(valor: decimal)

class PagamentoCartao implementa PagamentoStrategy:
    metodo pagar(valor):
        imprimir("Pagando R$" + valor + " no cartão")

class PagamentoBoleto implementa PagamentoStrategy:
    metodo pagar(valor):
        imprimir("Gerando boleto de R$" + valor)

7. Comparações com Outros Paradigmas

POO versus Programação Funcional

  • POO trabalha com estado mutável e objetos que encapsulam dados
  • Programação funcional prefere imutabilidade e funções puras
  • POO é mais intuitiva para modelar sistemas complexos com muitos estados

POO versus Programação Procedural

  • Procedural organiza código em funções e dados separados
  • POO agrupa dados e comportamentos em objetos coesos
  • POO facilita a manutenção em sistemas grandes

Quando Evitar POO

  • Problemas simples onde a abstração excessiva adiciona complexidade desnecessária
  • Sistemas com forte ênfase em transformações de dados (onde funcional é mais adequado)
  • Projetos muito pequenos onde a sobrecarga de design orientado a objetos não se justifica

Conclusão

A Programação Orientada a Objetos continua sendo um dos paradigmas mais influentes e amplamente adotados no desenvolvimento de software. Seus conceitos — encapsulamento, herança, polimorfismo, associações entre objetos e princípios como SOLID — fornecem uma base sólida para construir sistemas modulares, reutilizáveis e de fácil manutenção.

Dominar esses conceitos não significa aplicá-los cegamente em todas as situações, mas sim saber quando e como utilizá-los para criar soluções elegantes e eficientes. A POO é uma ferramenta poderosa no arsenal de qualquer desenvolvedor, e sua compreensão profunda é essencial para quem deseja construir software de qualidade profissional.

Referências