12 Design orientado a objetos
Encapsulamento, coesão e composição no chatbot
12.1 Objetivos de aprendizagem
Ao concluir este capítulo, você deverá ser capaz de:
- distribuir responsabilidades entre classes coesas;
- proteger invariantes com encapsulamento;
- escolher modificadores de acesso;
- usar composição para colaborar sem herança;
- identificar acoplamento desnecessário.
12.2 Da classe grande às responsabilidades
A versão anterior concentra entrada, regras e histórico. Uma primeira decomposição pode criar:
Historico: registra e consulta interações;ProcessadorComandos: escolhe respostas;AplicacaoConsole: coordena terminal e encerramento.
Uma classe é coesa quando seus dados e métodos trabalham para o mesmo objetivo. Acoplamento é a dependência entre partes; o objetivo não é eliminá-lo, mas depender de contratos pequenos e estáveis.
12.3 Encapsulamento e acesso
| Modificador | Acesso principal |
|---|---|
private |
somente a própria classe |
| sem modificador | classes do mesmo pacote |
protected |
mesmo pacote e subclasses |
public |
qualquer classe com acesso ao tipo |
Comece com a menor visibilidade necessária. Tornar um atributo private não basta: a classe também deve oferecer operações que preservem seu estado válido.
12.4 Composição
import java.util.ArrayList;
import java.util.List;
final class Historico {
private final List<String> linhas = new ArrayList<>();
void registrarInteracao(String entrada, String resposta) {
if (entrada == null || entrada.isBlank()) {
throw new IllegalArgumentException("Entrada obrigatória.");
}
linhas.add("USUÁRIO: " + entrada.strip());
linhas.add("BOT: " + resposta);
}
List<String> consultar() {
return List.copyOf(linhas);
}
int totalInteracoes() {
return linhas.size() / 2;
}
}
final class ProcessadorComandos {
String responder(String comando) {
return switch (comando) {
case "oi" -> "Olá!";
case "ajuda" -> "Comandos: oi, ajuda e dica.";
case "dica" -> "Prefira composição para relações do tipo tem-um.";
default -> "Não entendi.";
};
}
}
public class Capitulo11Design {
public static void main(String[] args) {
Historico historico = new Historico();
ProcessadorComandos processador = new ProcessadorComandos();
String entrada = "dica";
String resposta = processador.responder(entrada);
historico.registrarInteracao(entrada, resposta);
System.out.println(resposta);
System.out.println(historico.consultar());
}
}Historico não decide respostas. ProcessadorComandos não escreve no console nem conhece a estrutura do histórico. A aplicação compõe os objetos.
12.5 static e final
Use static para comportamento ou estado que pertence à classe, não a uma instância específica. Evite transformá-lo em atalho para estado global mutável.
Use final para comunicar que:
- uma referência não será reatribuída;
- um método não poderá ser sobrescrito;
- uma classe não foi projetada para herança.
12.6 Revisão de design
Pergunte:
- cada classe muda por um motivo principal?
- uma regra aparece em apenas um lugar?
- as coleções internas estão protegidas?
- seria possível trocar a interface de console sem reescrever o domínio?
- os nomes usam a linguagem do problema?
O exemplo completo está em examples/src/Capitulo11Design.java.
12.7 Prática verificável
Extraia da versão 10 uma classe Historico. A aplicação deve continuar produzindo as mesmas respostas.
Casos de teste:
- histórico vazio devolve uma lista vazia;
- uma interação gera exatamente duas linhas;
- alterar a lista devolvida não altera o estado interno;
- entrada vazia é rejeitada.
Use composição para relações “tem um”: o chatbot tem um histórico. Herança será reservada a relações legítimas de especialização.