Data-oriented programming (DOP) in Java emphasizes immutable data structures separated from business logic, leveraging modern features like records, sealed interfaces, and pattern matching for safer, more maintainable code.
Core Principles of DOP
DOP models data transparently using plain structures that fully represent domain concepts without hidden behavior or mutable state. Key rules include making data immutable, explicitly modeling all variants with sealed types, preventing illegal states at the type level, and handling validation at boundaries.
This contrasts with traditional OOP by keeping data passive and logic in pure functions, improving testability and reducing coupling.
Java's Support for DOP
Records provide concise, immutable data carriers with built-in equality and toString. Sealed interfaces define closed hierarchies for exhaustive handling, while pattern matching in switch and instanceof enables declarative operations on variants.
These features combine to enforce exhaustiveness at compile time, eliminating visitor patterns or runtime checks.
Practical Example: Geometric Shapes
Model 2D shapes to compute centers, showcasing DOP in action.
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Point(double x, double y) {}
record Circle(Point center, double radius) implements Shape {}
record Rectangle(Point topLeft, Point bottomRight) implements Shape {}
record Triangle(Point p1, Point p2, Point p3) implements Shape {}
Operations remain separate and pure:
public static Point centerOf(Shape shape) {
return switch (shape) {
case Circle c -> c.center();
case Rectangle r -> new Point(
(r.topLeft().x() + r.bottomRight().x()) / 2.0,
(r.topLeft().y() + r.bottomRight().y()) / 2.0
);
case Triangle t -> new Point(
(t.p1().x() + t.p2().x() + t.p3().x()) / 3.0,
(t.p1().y() + t.p2().y() + t.p3().y()) / 3.0
);
};
}
The sealed interface ensures exhaustive coverage, records keep data transparent, and the function is stateless.
DOP vs. Traditional OOP
| Aspect | DOP in Java | Traditional OOP |
|---|---|---|
| Data | Immutable records, sealed variants | Mutable objects with fields/methods |
| Behavior | Separate pure functions | Embedded in classes |
| State Handling | None; inputs → outputs | Mutable instance state |
| Safety | Compile-time exhaustiveness | Runtime polymorphism/overrides |
| Testing | Easy unit tests on functions | Mocking object interactions |
DOP shines in APIs, events, and rules engines by prioritizing data flow over object lifecycles.
Leave a Reply