-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSealedInterfaceDemo.java
More file actions
154 lines (139 loc) · 6.1 KB
/
Copy pathSealedInterfaceDemo.java
File metadata and controls
154 lines (139 loc) · 6.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package Phase3_ObjectOrientation.Interfaces;
/**
* Sealed Interfaces - Java 17 (LTS) onwards
* -----------------------------------------
* A SEALED INTERFACE restricts WHO IS ALLOWED TO IMPLEMENT IT. Before sealed
* interfaces, every public interface was open to the entire world. Sealed
* interfaces close that hole - you list the permitted implementors.
* <p>
*
* sealed interface Result permits Success, Failure {}
* record Success(String data) implements Result {}
* record Failure(String err) implements Result {}
* <p>
*
* Three Choices Each Permitted Subtype Must Make
* ----------------------------------------------
* 1. final - cannot be extended further. (record is implicitly final)
* 2. sealed - extendable, but with its OWN permits list.
* 3. non-sealed - extendable by anyone; ends the seal.
* <p>
*
* Why Sealed?
* -----------
* - ALGEBRAIC DATA TYPES - model a closed union of variants.
* - EXHAUSTIVE SWITCH - the compiler verifies every permitted case is
* handled; no `default` needed.
* - VERSION SAFETY - adding a new permitted variant turns every
* previously-exhaustive switch into a compile
* error - exactly what you want.
* - DOCUMENTATION - the permits list IS the documentation of "all
* implementors".
* <p>
*
* Common Patterns
* ---------------
* Result<T, E> Success | Failure
* Json Null | Boolean | Number | String | Array | Object
* Token Plus | Minus | Number | Eof
* HttpResponse Ok | Redirect | NotFound | ServerError
* <p>
*
* Rules
* -----
* - Permitted subtypes must live in the SAME MODULE (or the same package
* if no module).
* - When permitted subtypes are in the SAME FILE the `permits` clause may
* be omitted - the compiler infers it. This file uses that shortcut.
* - Every permitted subtype must DIRECTLY implement the sealed interface.
* <p>
*
* Related: SealedClassesDemo.java in OOPSConcepts/SealedClasses shows the
* same idea on classes; sealed interfaces tend to be more common because
* they pair naturally with records.
*/
public class SealedInterfaceDemo {
// ============================================================
// A sealed interface representing the closed set of HTTP results.
// ============================================================
sealed interface HttpResult /* permits Ok, Redirect, NotFound, ServerError */ { }
// Records are implicitly final - meets the "final" subtype requirement.
record Ok(String body) implements HttpResult {}
record Redirect(String location) implements HttpResult {}
record NotFound(String resource) implements HttpResult {}
record ServerError(int code, String reason) implements HttpResult {}
// ============================================================
// A function that maps the result to a human-readable message.
// Notice: NO `default` branch - the compiler verifies completeness.
// ============================================================
static String describe(HttpResult r) {
return switch (r) {
case Ok o -> "200 OK: " + o.body();
case Redirect d -> "302 -> " + d.location();
case NotFound n -> "404 not found: " + n.resource();
case ServerError s -> "5xx (" + s.code() + "): " + s.reason();
};
}
// ============================================================
// Another sealed interface, this time showing a non-sealed subtype.
// ============================================================
sealed interface Shape permits Circle, Polygon { }
record Circle(double radius) implements Shape {}
/**
* Polygon is non-sealed, so anyone may extend it further. That is a
* pragmatic choice: we want a closed CATEGORY (Circle vs Polygon) but
* unlimited variety of Polygons.
*/
non-sealed interface Polygon extends Shape {
int sides();
}
record Triangle(double a, double b, double c) implements Polygon {
@Override public int sides() { return 3; }
}
record Pentagon(double side) implements Polygon {
@Override public int sides() { return 5; }
}
public static void main(String[] args) {
section("1) Exhaustive switch on a sealed interface");
HttpResult[] outcomes = {
new Ok("hello"),
new Redirect("/login"),
new NotFound("/users/99"),
new ServerError(503, "database down")
};
for (HttpResult r : outcomes) {
System.out.println(describe(r));
}
section("2) Sealed + non-sealed - closed category, open variety");
Shape[] shapes = {
new Circle(2.0),
new Triangle(3, 4, 5),
new Pentagon(2.0)
};
for (Shape s : shapes) {
String label = switch (s) {
case Circle c -> "Circle (r=" + c.radius() + ")";
case Polygon p -> p.getClass().getSimpleName() + " (" + p.sides() + " sides)";
};
System.out.println(label);
}
section("3) Adding a new variant fails the build elsewhere");
// If you add `record Gone(String resource) implements HttpResult {}`
// to HttpResult's permits list, the `describe()` switch above will no
// longer compile until you add a case for it. That compiler nudge is
// the whole point of sealed.
// OUTPUT
// ====== 1) Exhaustive switch on a sealed interface ======
// 200 OK: hello
// 302 -> /login
// 404 not found: /users/99
// 5xx (503): database down
// ====== 2) Sealed + non-sealed - closed category, open variety ======
// Circle (r=2.0)
// Triangle (3 sides)
// Pentagon (5 sides)
}
private static void section(String title) {
System.out.println("\n====== " + title + " ======");
}
}