【Kotlin】sealed class 在Kotlin 中, sealed class(密封類)是一種特殊的類,它用來表示受限的類層次結構。 sealed class及其子類別的定義必須在同一個檔案中,從而確保了編譯時的類型檢查。這種類別通常用於表達某種有限數量的可能狀態或類型。以下是 sealed class的一些主要特點和用途: 特點 受限層次結構: sealed class的所有直接子類別必須在同一個檔案中定義,這樣可以確保在編譯時知道所有可能的子類別。 抽象類別: sealed class本身是抽象的,不能直接實例化,只能透過其子類別來實例化。 強制列舉:在使用 when表達式時,如果覆寫了所有的子類別分支,就不需要再加 else分支,這樣可以在編譯時確保列舉所有情況。 使用場景 需要有限的類繼承:您有一組預先定義的、有限的子類來擴展一個類,所有這些子類在編譯時都是已知的。 需要類型安全設計:安全性和模式匹配在您的專案中至關重要。特別是對於狀態管理或處理複雜的條件邏輯。 使用封閉式 API:您需要為程式庫提供強大且可維護的公共 API,以確保第三方用戶端按預期使用 API。 UI 應用程式中的狀態管理 可以使用sealed來表示應用程式中的不同 UI 狀態。 這種方法允許結構化且安全地處理 UI 變更。 此範例示範如何管理各種 UI 狀態: sealed class UIState { data object Loading : UIState() data class Success(val data: String) : UIState() data class Error(val exception: Exception) : UIState() } fun updateUI(state: UIState) { when (state) { is UIState.Loading -> showLoadingIndicator() is UIState.Success -> showData(state.data) is UIState.Error -> showError(state.exception) } } 付款方式處理 實際業務應用中,高效處理各種支付方式是常見的需求。您可以使用具有 when表達式的密封類別來實現此類業務邏輯。透過將不同的支付方式表示為密封類的子類,它建立了一個清晰且可管理的交易處理結構: sealed class Payment { data class CreditCard(val number: String, val expiryDate: String) : Payment() data class PayPal(val email: String) : Payment() data object Cash : Payment() } fun processPayment(payment: Payment) { when (payment) { is Payment.CreditCard -> processCreditCardPayment(payment.number, payment.expiryDate) is Payment.PayPal -> processPayPalPayment(payment.email) Payment.Cash -> processCashPayment() } } Payment是一個密封類,代表電子商務系統中的不同支付方式: CreditCard、 PayPal和 Cash。每個子類別都可以有其特定的屬性,例如 number和 expiryDatefor CreditCard和 emailfor  PayPal。 此 processPayment()函數示範如何處理不同的付款方式。這種方法確保考慮所有可能的付款類型,並且系統對於將來添加新的支付方式保持靈活性。 API 請求-回應處 您可以使用sealed class和密sealed interface來實作處理 API 請求和回應的使用者驗證系統。 使用者認證系統具有登入和登出功能。 介面 ApiRequest定義了特定的請求類型: LoginRequest登入和 LogoutRequest登出操作。sealed class ApiResponse封裝了不同的回應場景: UserSuccess使用使用者資料、 UserNotFound針對缺席的使用者以及 Error針對任何故障。 此 handleRequest函數使用表達式以類型安全的方式處理這些請求 when,同時 getUserById模擬使用者檢索: // Import necessary modules import io.ktor.server.application.* import io.ktor.server.resources.* import kotlinx.serialization.* /////////////////////////////////////////////////////////////////////////////////////// // Define the sealed interface for API requests using Ktor resources @Resource("api") sealed interface ApiRequest @Serializable @Resource("login") data class LoginRequest(val username: String, val password: String) : ApiRequest @Serializable @Resource("logout") object LogoutRequest : ApiRequest /////////////////////////////////////////////////////////////////////////////////////// // Define the ApiResponse sealed class with detailed response types sealed class ApiResponse { data class UserSuccess(val user: UserData) : ApiResponse() data object UserNotFound : ApiResponse() data class Error(val message: String) : ApiResponse() } /////////////////////////////////////////////////////////////////////////////////////// // User data class to be used in the success response data class UserData(val userId: String, val name: String, val email: String) /////////////////////////////////////////////////////////////////////////////////////// // Function to validate user credentials (for demonstration purposes) fun isValidUser(username: String, password: String): Boolean { // Some validation logic (this is just a placeholder) return username == "validUser" && password == "validPass" } /////////////////////////////////////////////////////////////////////////////////////// // Function to handle API requests with detailed responses fun handleRequest(request: ApiRequest): ApiResponse { return when (request) { is LoginRequest -> { // 合法使用者 if (isValidUser(request.username, request.password)) { ApiResponse.UserSuccess(UserData("userId", "userName", "userEmail")) } else { ApiResponse.Error("Invalid username or password") } } is LogoutRequest -> { // Assuming logout operation always succeeds for this example ApiResponse.UserSuccess(UserData("userId", "userName", "userEmail")) // For demonstration } } } // Function to simulate a getUserById call fun getUserById(userId: String): ApiResponse { return if (userId == "validUserId") { ApiResponse.UserSuccess(UserData("validUserId", "John Doe", "john@example.com")) } else { ApiResponse.UserNotFound } // Error handling would also result in an Error response. } // Main function to demonstrate the usage fun main() { // 使用帳密登入 val loginResponse = handleRequest(LoginRequest("user", "pass")) println(loginResponse) // 登出 val logoutResponse = handleRequest(LogoutRequest) println(logoutResponse) // 檢索使用者 (存在,回應成功) val userResponse = getUserById("validUserId") println(userResponse) // 檢索使用者 (不存在,回應失敗) val userNotFoundResponse = getUserById("invalidId") println(userNotFoundResponse) } Java 中的 sealed 關鍵字 Java 17 引入了 sealed 類型,允許你限制哪些類可以繼承這個類。 public sealed class Shape permits Circle, Rectangle, NotAShape { } public final class Circle extends Shape { private final double radius; public Circle(double radius) { this.radius = radius; } public double getRadius() { return radius; } } public final class Rectangle extends Shape { private final double width; private final double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } public double getWidth() { return width; } public double getHeight() { return height; } } public final class NotAShape extends Shape { } C# 中的 sealed 關鍵字 當你將一個類別標記為 sealed 時,這意味著這個類別不能被其他類別繼承。這在需要明確表達某個類別不能被擴展時非常有用,例如當你想要保證某個類別的行為不被改變時。 public sealed class MyClass { public void Display() { Console.WriteLine("Hello from MyClass"); } } // 這將導致編譯錯誤,因為 MyClass 是 sealed 的 // public class DerivedClass : MyClass // { // } public class Program { public static void Main() { MyClass myClass = new MyClass(); myClass.Display(); } }