Programação Orientada a Objetos¶
A programação orientada a objetos (POO) modela um programa como objetos que colaboram com estado, comportamento, identidade e responsabilidades explícitas. Java suporta POO, mas nem toda classe Java representa um objeto de domínio útil e nem todo problema é melhor expresso por herança.
Objetivos de aprendizado¶
- distinguir encapsulamento de simplesmente tornar campos privados;
- distinguir abstração, herança, subtipagem e polimorfismo;
- explicar despacho dinâmico e substitutabilidade;
- preferir composição quando herança não representa um subtipo estável; e
- projetar objetos que preservam seus invariantes.
Objetos e classes¶
Uma classe define um tipo e um modelo de implementação. Um objeto é uma instância de tempo de execução. Variáveis Java de tipo classe ou interface normalmente contêm referências; uma atribuição copia uma referência, não o objeto referenciado.
final class BankAccount {
private long balanceInCents;
BankAccount(long openingBalanceInCents) {
if (openingBalanceInCents < 0) {
throw new IllegalArgumentException("negative opening balance");
}
balanceInCents = openingBalanceInCents;
}
void withdraw(long amountInCents) {
if (amountInCents <= 0 || amountInCents > balanceInCents) {
throw new IllegalArgumentException("invalid withdrawal");
}
balanceInCents -= amountInCents;
}
long balanceInCents() {
return balanceInCents;
}
}
O encapsulamento relevante não é apenas o modificador private. Toda operação
pública protege o invariante balanceInCents >= 0; chamadores não podem colocar o
objeto em um estado inválido por meio de um setter irrestrito.
Abstração¶
Uma abstração expõe comportamento relevante a um cliente enquanto esconde decisões que o cliente não deveria depender. Interfaces podem expressar um papel:
A interface não promete como descontos são armazenados ou calculados. Uma abstração útil é definida por um contrato comportamental, não apenas por assinaturas de método.
Herança e subtipagem¶
Herança de classe reutiliza ou especializa implementação. Subtipagem cria uma relação de substitutabilidade: código escrito para um supertipo deve continuar a satisfazer suas expectativas quando dado um subtipo.
sealed interface Shape permits Circle, Rectangle {
double area();
}
record Circle(double radius) implements Shape {
Circle {
if (!(radius >= 0.0)) throw new IllegalArgumentException("negative radius");
}
@Override public double area() {
return Math.PI * radius * radius;
}
}
record Rectangle(double width, double height) implements Shape {
Rectangle {
if (!(width >= 0.0) || !(height >= 0.0)) {
throw new IllegalArgumentException("negative dimension");
}
}
@Override public double area() {
return width * height;
}
}
Chamar area() por meio de uma referência Shape usa despacho dinâmico para selecionar a
implementação em tempo de execução. Sobrecarga é diferente: o compilador seleciona entre
assinaturas de método de mesmo nome usando tipos em tempo de compilação.
Composição sobre herança de implementação¶
Composição delega uma responsabilidade a um colaborador:
final class PriceCalculator {
private final DiscountPolicy discounts;
PriceCalculator(DiscountPolicy discounts) {
this.discounts = Objects.requireNonNull(discounts);
}
Money totalFor(Order order) {
return order.subtotal().subtract(discounts.discountFor(order));
}
}
Políticas diferentes podem variar independentemente de PriceCalculator. Herança é
apropriada quando o contrato do subtipo é genuíno e estável; use composição
quando o objetivo é apenas reutilizar ou substituir comportamento.
Identidade e valor¶
Alguns objetos representam uma identidade que persiste enquanto atributos mudam. Outros representam valores e são iguais apenas por seu conteúdo. Objetos de valor imutáveis são bons candidatos para registros. Igualdade de entidade precisa de uma política de identidade consciente do ciclo de vida, particularmente quando persistência gera identificadores.
Erros comuns¶
- objeto anêmico com setters públicos que não podem defender seus invariantes;
- uma hierarquia profunda construída apenas para reutilização de código;
- um "objeto deus" coordenando responsabilidades não relacionadas;
- expor coleções internas mutáveis;
- usar herança onde um subtipo fortalece pré-condições ou surpreende clientes;
- presumir que estado privado torna um objeto thread-safe.
Exercícios¶
- Refatorar cálculo condicional de pagamento em políticas compostas.
- Explicar por que um quadrado com largura e altura mutáveis independentemente não é seguramente substituível para essa abstração de retângulo.
- Projete um objeto de valor que mantenha defensivamente a posse de uma coleção.