Abstraction picks the right view
A Cart should expose addItem, removeItem, and total, not every intermediate storage detail or persistence-specific concern.
Developers often hear abstraction and encapsulation together, but they solve different problems. Abstraction chooses the right surface. Encapsulation protects the inside. In OOAD, that distinction matters because a class can have a simple API and still leak too much, or it can hide state while still exposing the wrong business model.
In e-commerce systems, objects often sit at the center of changing rules: minimum order totals, shipment eligibility, refund windows, fraud holds, or notification timing. If the model does not decide what to show and what to guard, the rules leak outward into services, controllers, and utilities. That is how codebases become procedural while still using classes.
Abstraction is about relevance. It asks: which operations and concepts matter to the caller? Encapsulation is about protection. It asks: which details and rules must remain under the object’s control?
A well-designed Java object usually combines both. It exposes a domain-level contract, then keeps the internal state and decision logic hidden behind that contract. That does not mean every field must be private just to satisfy style. It means the object should own the decisions that define its correctness.
In OOAD work, abstraction and encapsulation usually show up before patterns do. They are the first design tools you use when deciding whether a domain type should own behavior or whether a service, policy, or repository should take responsibility instead.
A Cart should expose addItem, removeItem, and total, not every intermediate storage detail or persistence-specific concern.
A Shipment should guard whether it can move from CREATED to DISPATCHED instead of letting any caller assign statuses directly.
When callers depend on stable domain-facing behavior instead of internal detail, refactoring becomes safer.
package org.javaomnibus.ecommerce.foundations;
import java.time.Instant;
public final class Shipment {
private final ShipmentId shipmentId;
private ShipmentStatus status;
private Instant dispatchedAt;
public Shipment(ShipmentId shipmentId) {
this.shipmentId = shipmentId;
this.status = ShipmentStatus.CREATED;
}
public ShipmentView view() {
return new ShipmentView(shipmentId.value(), status.name(), dispatchedAt);
}
public void dispatch() {
if (status != ShipmentStatus.CREATED) {
throw new IllegalStateException("Only created shipments can be dispatched");
}
this.status = ShipmentStatus.DISPATCHED;
this.dispatchedAt = Instant.now();
}
}
record ShipmentView(String shipmentId, String status, Instant dispatchedAt) {}
record ShipmentId(String value) {}
enum ShipmentStatus { CREATED, DISPATCHED }
Too much hiding can make a model awkward if all meaningful behavior gets wrapped in trivial methods. The goal is not defensive ceremony. The goal is to protect real business invariants and expose the right domain language.