簡體   English   中英

用非密封類擴展密封類有什么意義?

[英]What is the point of extending a sealed class with a non-sealed class?

我真的不明白為什么在 JEP 360/Java 15 中有一個non-sealed關鍵字。對我來說,密封類的擴展應該只是 final 或密封類本身。

提供“非密封”關鍵字將邀請開發人員進行黑客攻擊。 為什么我們允許將密封類擴展到非密封類?

因為在現實世界的 API 中,有時我們希望在限制其他擴展點的同時支持特定的擴展點。 不過, Shape示例並不是特別令人回味,這就是為什么允許它看起來很奇怪的原因。

密封類是關於更好地控制可以擴展給定的可擴展類型。 您可能想要這樣做有幾個原因,“確保沒有人擴展層次結構”只是其中之一。

在很多情況下,一個 API 有幾個“內置”抽象,然后是一個“逃生艙”抽象; 這允許 API 作者將潛在的擴展者引導到為擴展而設計的逃生艙口。

例如,假設您有一個使用Command模式的系統,有幾個您想要控制其實現的內置命令,以及一個用於擴展的UserPluginCommand

sealed interface Command
    permits LoginCommand, LogoutCommand, ShowProfileCommand, UserPluginCommand { ... }

// final implementations of built-in commands

non-sealed abstract class UserPluginCommand extends Command {
    // plugin-specific API
}

這樣的層次結構完成了兩件事:

  • 所有擴展都通過UserPluginCommand ,可以為擴展進行防御性設計並提供適合用戶擴展的 API,但我們仍然可以在設計中使用基於接口的多態性,因為不會出現完全不受控制的子類型;

  • 系統仍然可以依賴以下事實,即四種允許的類型涵蓋了 Command 的所有實現。 所以內部代碼可以使用模式匹配並對其詳盡無遺充滿信心:

switch (command) {
    case LoginCommand(...): ... handle login ...;
    case LogoutCommand(...): ... handle logout ...;
    case ShowProfileCommand(...): ... handle query ...;
    case UserPluginCommand uc: 
        // interact with plugin API
    // no default needed, this switch is exhaustive

UserPluginCommand可能有無數子類型,但系統仍然可以自信地推斷出它可以用這四種情況覆蓋海濱。

在 JDK 中利用這一點的 API 的一個示例是java.lang.constant ,其中有兩個為擴展而設計的子類型——動​​態常量和動態調用點。

我認為JEP 360中的以下示例正在顯示它:

package com.example.geometry;

public abstract sealed class Shape
    permits Circle, Rectangle, Square {...}

public final class Circle extends Shape {...}

public sealed class Rectangle extends Shape 
    permits TransparentRectangle, FilledRectangle {...}
public final class TransparentRectangle extends Rectangle {...}
public final class FilledRectangle extends Rectangle {...}

public non-sealed class Square extends Shape {...}

您只想允許指定的類擴展Shape 現在讓Square non-sealed有什么意義? 因為您希望允許任何其他類擴展Square (和層次結構)。

可以這樣想:任何想要擴展Shape的類都必須使用CircleRectangleSquare in between 因此,此子層次結構的每個擴展類都將是CircleRectangleSquare (is-a 關系)。

sealed / non-sealed組合允許您僅“密封”層次結構的一部分,而不是全部(從根開始)。


請注意JEP 360告訴我們有關允許的類的內容:

每個允許的子類都必須選擇一個修飾符來描述它如何繼續由其超類發起的密封:

選項有: final 、 seal 或non-sealed sealed 你被迫顯式,所以我們需要non-sealed來“打破密封”。


Brian Goetz發布了一個真實的用例並解釋了現實生活中的好處。 我想添加另一個示例:

想象一下,您正在開發一款包含英雄和怪物的游戲。 有些類可能是這樣的:

public sealed class Character permits Hero, Monster {}

public sealed class Hero extends Character permits Jack, Luci {}
public non-sealed class Monster extends Character {}

public final class Jack extends Hero {}
public final class Luci extends Hero {}

游戲有兩個主角,有幾個敵人。 主要角色是一成不變的,可以有盡可能多的不同怪物。 游戲中的每個角色要么是英雄,要么是怪物。

這是一個最小的例子,希望能更能說明問題,並且可能會有一些變化,例如添加一個類CustomHero使模組制作者能夠創建自定義英雄。

想象一下,您想在代碼中編寫一個Shape類。 如果你不想實例化它,你也可以使這個類abstract 您想在某些類中擴展它,例如CircleTriangleRectangle 現在您已經實現了它,您希望確保沒有人能夠擴展您的Shape類。 沒有sealed關鍵字你能做到嗎?
不,因為您必須將其設為final ,在這種情況下,您將無法將其擴展為具有任何子類。 這就是sealed關鍵字的用武之地! 您使抽象類密封並限制哪些類能夠擴展它:

public abstract sealed class Shape 
    permits Circle, Triangle, Rectangle {...}

請記住,如果您的子類與Shape類不在同一個包中,則必須在包中提及它們的名稱:

public abstract sealed class Shape
    permits com.example.Circle    { ... }

現在,當您聲明這三個子類時,您必須將它們設為finalsealednon-sealed (應該使用這些修飾符中的一個且只使用一個)

現在什么時候可以在所需的類中擴展Circle 只有當你告訴 Java 它可以通過使用non-sealed關鍵字由未知子類擴展時:

public non-sealed class Circle {...} 

根據此文檔non-sealed類允許向世界開放部分繼承層次結構。 這意味着根密封類只允許一組封閉的子類對其進行擴展。

但是,子類仍然可以通過使用非密封關鍵字允許自己被任意數量的子類擴展。

public sealed class NumberSystem
    // The permits clause has been omitted
    // as all the subclasses exists in the same file.
{ }
final class Binary extends NumberSystem { .. }

final class Octal extends NumberSystem { .. }

final class HexaDecimal extends NumberSystem { .. }

non-sealed class Decimal extends NumberSystem { .. }

final class NonRecurringDecimal extends Decimal {..}
final class RecurringDecimal extends Decimal {..}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM