นากกายสีมิ้นต์ต้นฉบับเฝ้าประตูแตกแขนงสามบานที่เชื่อมจากตรากลางสีอำพัน

Java Sealed Classes: ปิดขอบเขต hierarchy ให้ compiler ช่วยเฝ้า

น้องกายEngineering

interface ปกติเปิดให้ class ใด implement ก็ได้ เหมาะกับ extension point แต่ไม่เหมาะกับ domain ที่ชนิดย่อยต้องมีจำนวนจำกัด เช่นผลลัพธ์สำเร็จหรือผิดพลาด sealed ทำให้เจ้าของชนิดประกาศรายชื่อ subclass ที่อนุญาตและ compiler ปฏิเสธคนนอก

WHY: closed world ทำให้กติกาตรวจได้

เมื่อ hierarchy ปิด ทีมรู้ทุกกรณีที่เป็นไปได้และกล้า refactor มากขึ้น หากเพิ่ม variant ใหม่ จุดที่ประมวลผลผลลัพธ์ควรถูกทบทวน ต่างจาก convention ใน comment ที่ implementation ใหม่อาจเล็ดลอดโดยไม่มีใครรู้

Java 17 กำหนดให้ permitted subclass ต้องเป็น final, sealed หรือ non-sealed เพื่อบอกชัดว่ากิ่งนั้นหยุด ขยายแบบควบคุม หรือเปิดต่อ ตัวอย่างนี้หยุดทุกกิ่งด้วย records:

public class Main {
    sealed interface Result permits Success, Failure {}

    record Success(int value) implements Result {}

    record Failure(String message) implements Result {}

    static String describe(Result result) {
        if (result instanceof Success success) {
            return "value=" + success.value();
        }
        if (result instanceof Failure failure) {
            return "error=" + failure.message();
        }
        throw new IllegalStateException("unreachable result type");
    }

    public static void main(String[] args) {
        assert describe(new Success(42)).equals("value=42");
        assert describe(new Failure("timeout")).equals("error=timeout");
    }
}

ใช้กับ domain boundary ไม่ใช่ทุก interface

Sealed hierarchy เหมาะกับ expression tree, protocol message, result และ state machine ที่เจ้าของต้องควบคุมครบชุด ไม่เหมาะกับ SPI, plugin หรือ library interface ที่ตั้งใจให้ผู้ใช้นอก module ขยาย การ seal extension point จะเปลี่ยนจาก ecosystem เป็น whitelist และสร้างแรงเสียดทานทุกครั้งที่มี use case ใหม่

permitted types ต้องอยู่ใกล้ตามข้อจำกัดของ package/module ซึ่งช่วยให้ ownership ชัด แต่ migration จาก public interface เดิมอาจเป็น breaking change สำหรับ consumer ที่มี implementation ของตน ต้องค้น downstream ก่อน

บน Java 17 ยังต้องมี fallback หลัง instanceof chain ตามตัวอย่าง รุ่นใหม่กว่านี้มี pattern switch ที่ช่วย exhaustiveness มากขึ้น แต่อย่านำ syntax preview มาใส่ codebase Java 17 เพียงเพื่อให้ตัวอย่างสั้น

Recap

seal เฉพาะ hierarchy ที่เป็น closed domain บังคับทุกกิ่งประกาศนโยบายขยาย และทดสอบทุก permitted variant งานถัดไปคือหา enum ที่เริ่มแบกข้อมูลต่างรูปกันมากเกินไป แล้วทดลอง model เป็น sealed interface กับ records ใน branch เล็กครับ