Pilares de Orientação a Objetos em Java
Guia Completo sobre Visibilidade, Encapsulamento, Alta Coesão e Relacionamentos entre Objetos
Visibilidade e Modificadores de Acesso
Os objetos em Java encapsulam atributos e comportamentos. O acesso direto e indiscriminado às propriedades internas de um objeto é uma prática arriscada, pois impede a validação de regras de negócio.
Para gerenciar a visibilidade e proteger o estado de nossas classes, utilizamos os modificadores de acesso:
| Modificador | Símbolo UML | Descrição e Nível de Acesso |
|---|---|---|
| public | + | Livre acesso para qualquer classe em qualquer pacote do sistema. |
| private | - | Acessível única e exclusivamente pelos métodos de dentro da própria classe. |
| protected | # | Acessível por classes do mesmo pacote e classes filhas (através de herança). |
| default (sem palavra-chave) | Nenhum | Acessível apenas por classes declaradas no mesmo pacote físico. |
Encapsulamento, Métodos Especiais e Alta Coesão
2.1 O que é Encapsulamento?
O **encapsulamento** consiste em ocultar os detalhes de implementação e o estado interno de uma classe, expondo apenas uma interface pública de métodos. Definimos os atributos como `private` e controlamos sua leitura/escrita por meio de métodos especiais: **Getters** (leitura) e **Setters** (escrita).
Barreira de Segurança: Getters/Setters controlando o acesso a atributos Privados
2.2 O Conceito de Alta Coesão
Uma classe possui alta coesão quando é extremamente focada e tem uma única responsabilidade. As validações das regras de negócio devem ocorrer dentro dela mesma. Se permitirmos que classes externas alterem ou façam as validações do nosso objeto, criamos dependências acopladas e perigosas (baixa coesão).
Exemplo Prático: Validação Interna com Construtores Coesos
Observe no código abaixo que o construtor chama o método `setIdade()` em vez de atribuir o valor diretamente. Isso garante que a validação de barreira seja executada mesmo na instanciação:
public class Paciente {
private int idade; // Atributo protegido (private)
// Construtor vazio
public Paciente() {
}
// Construtor parametrizado chamando o setter para aplicar a regra
public Paciente(int idade) {
this.setIdade(idade);
}
// Getter tradicional
public int getIdade() {
return this.idade;
}
// Setter com validação interna (Alta Coesão)
public void setIdade(int idade) {
if (idade <= 0 || idade > 120) {
System.out.println("[Alerta] Idade inválida de paciente ignorada!");
this.idade = 1; // Valor padrão de consistência
} else {
this.idade = idade;
}
}
}
Associação Simples (1 para 1)
Uma nova classe em Java funciona como um tipo de dado personalizado. O relacionamento de **associação** ocorre quando usamos instâncias de uma classe como atributos internos de outra.
Imagine uma classe `Sorvete` que possui uma associação de um-para-um com a classe `Calda`:
// Classe auxiliar
public class Calda {
private String sabor;
public Calda(String sabor) {
this.sabor = sabor;
}
public String getSabor() { return this.sabor; }
}
// Classe Principal com associação 1-1
public class Sorvete {
private String saborSorvete;
private Calda caldaAdicionada; // Associação 1-1
public Sorvete(String saborSorvete) {
this.saborSorvete = saborSorvete;
}
public Calda getCalda() { return this.caldaAdicionada; }
public void setCalda(Calda calda) { this.caldaAdicionada = calda; }
}
Associação de Um para Muitos com ArrayList
Quando precisamos expandir a relação para permitir múltiplos objetos associados (ex: um sorvete com várias caldas), recorremos a coleções dinâmicas como o `ArrayList` da biblioteca padrão do Java.
Regra de Ouro do ArrayList
Por se tratar de um objeto complexo, um `ArrayList` **deve ser obrigatoriamente instanciado** no construtor da classe proprietária. Se você esquecer essa inicialização, tentar acessar um membro de uma referência `null` causará um erro em tempo de execução do tipoNullPointerException.
import java.util.ArrayList; // Importação da estrutura dinâmica
public class SorveteCompleto {
private String sabor;
private ArrayList<Calda> caldas; // Associação de 1 para N
public SorveteCompleto(String sabor) {
this.sabor = sabor;
// Instanciação preventiva do objeto lista no construtor
this.caldas = new ArrayList<Calda>();
}
public String getSabor() {
return this.sabor;
}
// Setter adaptado para inserção unitária de objetos
public void adicionarCalda(Calda calda) {
this.caldas.add(calda);
}
public ArrayList<Calda> getCaldas() {
return this.caldas;
}
}
Manipulação e Iteração com For-Each
Para recuperar e iterar de forma limpa e legível os elementos armazenados dentro de uma coleção de objetos, utilizamos a simplificada estrutura do laço for-each:
public class AppSorveteria {
public static void main(String[] args) {
SorveteCompleto sorvete = new SorveteCompleto("Baunilha");
// Instanciando caldas e associando ao sorvete
sorvete.adicionarCalda(new Calda("Chocolate"));
sorvete.adicionarCalda(new Calda("Morango"));
System.out.println("Sorvete de: " + sorvete.getSabor());
System.out.println("Caldas Adicionadas:");
// Laço for-each para percorrer a coleção associada
for (Calda c : sorvete.getCaldas()) {
System.out.println("- Sabor da calda: " + c.getSabor());
}
}
}
Prática de Laboratório (Mãos à Obra!)
Desafio Prático: O Carrinho de Compras
Modele e implemente em Java a relação entre um `Carrinho` de compras e os seus `Itens` (com nome do produto e preço). Use encapsulamento completo e uma associação de 1-para-Muitos.
Requisitos de Desenvolvimento:
- Crie a classe `Item` com atributos `nome` (String) e `preco` (double), com construtor e getters.
- Crie a classe `Carrinho` que contém uma lista de itens (`ArrayList<Item>`).
- Garanta a instanciação da lista no construtor da classe `Carrinho`.
- Crie um método `adicionarItem(Item item)` e um método `calcularTotal()` que varre a lista somando o preço de cada item e retorna o total acumulado.
// Item.java
public class Item {
private String nome;
private double preco;
public Item(String nome, double preco) {
this.nome = nome;
this.preco = preco;
}
public String getNome() { return this.nome; }
public double getPreco() { return this.preco; }
}
// Carrinho.java
import java.util.ArrayList;
public class Carrinho {
private ArrayList<Item> itens;
public Carrinho() {
this.itens = new ArrayList<Item>(); // Instanciação protetiva
}
public void adicionarItem(Item item) {
this.itens.add(item);
}
public double calcularTotal() {
double total = 0.0;
for (Item item : this.itens) {
total += item.getPreco();
}
return total;
}
}
Mapeamento Finalizado
Este módulo de encapsulamento e associação estabelece as bases lógicas necessárias para compreender a alocação dinâmica e referências de memória. Na próxima aula de laboratório, estudaremos a estrutura sequencial de **Pilhas**.
Materiais Complementares de Apoio
Slides da Aula: Encapsulamento & Associação
Acesse o slide de apoio didático contendo toda a parte teórica sobre visibilidade, getters/setters e a introdução ao ArrayList em Java.
Videoaula Relacionada: Encapsulamento e Relacionamentos
Assista à explicação em vídeo focada em modificadores de acesso, coesão de classes e associações simples e múltiplas.
Repositório de Código Fonte
Explore, acesse e faça o download de todos os códigos-fonte de exemplo e exercícios estruturados nesta trilha.