簡體   English   中英

如何使具有有界類型參數的方法排除一個子類?

[英]How can I make a method with a bounded type parameter exclude one subclass?

讓我們假設長臂猿、猩猩、大猩猩、黑猩猩和人類都是猿。 我已經相應地模擬了這種關系。

class Ape {}
class Gibbon extends Ape {}
class Orangutan extends Ape {}
class Gorilla extends Ape {}
class Chimpanzee extends Ape {}
class Human extends Ape {}

我現在想編寫一個帶有有界類型參數的hunt() 方法來狩獵猿類。

public static <T extends Ape> void hunt(T type) {}

讓我們假設獵殺非人類猿沒有錯,因為它們只是動物。 然而,獵殺人類是錯誤的,因為這是謀殺。 如何重寫上述有界類型參數以將人類排除為合法參數? 我對這里的例外不感興趣。 如果使用人工參數調用,我根本不希望hunt() 方法編譯。

我不相信有辦法做到這一點。 您可以創建一個排除 Humans 的 Ape 子類型,或者您可以在方法本身中檢查 Humans 作為類型(這將違反開放-封閉原則):

public static <T extends Ape> void hunt(T type) {
  if (type instanceOf Human) {
     throw new IllegalArgumentException("It's immoral to hunt Humans.  You monster.");
  }
  . . .
}

你不能按照你想要的方式排除一個特定的子類。 但是,您可以做的是創建一個接口“isHuntable”,由您想要狩獵的所需動物實現,然后將其用作方法的類型,而不是泛型類型綁定。 另一個可能不太優雅的解決方案是在您的層次結構中創建另一個稱為“猴子”的級別,例如從 Ape 擴展而來,所有動物都從它擴展而來,並且您將這種 Monkey 類型用於您的方法。 不過我會采用第一種方法。 您可以使用顯式類型檢查,但您會破壞代碼中的“開閉”原則,因此最好將這些檢查用於類型系統。

只是對接口行為契約的概念進行了擴展,這是一個功能強大的工具,可惜沒有得到充分利用。 你在你的類人猿之間是一種“是一種”關系,它把你的主題緊密結合在一起。 另一方面,“可狩獵”不是該層次結構的固有或定義特征,而是您想添加到該層次結構子集的附加條件/行為/檢查。 通過將合同(接口實現)添加到您打算具有該行為的子集,可以更好地實現這一點。 這將允許在未來以最少的重構、最大的語義清晰度、更少的錯誤和不將類型與行為緊密綁定的情況下添加更多的契約(接口實現)。 簡而言之,您不會違反 Liskov 替換原則、開閉原則或任何 SOLID 原則。

你不可以做這個。 沒有辦法指定一個綁定說“任何子類,除了這個”。

即使你可以,它也不會阻止你調用它:

chimp.hunt((Ape) human);

會繞過它。

您所能做的就是在運行時檢查這一點。

您可以通過諸如Error Prone 之類的工具(由 Google 編寫,我是一名員工,我做出貢獻;其他工具可用)編寫編譯時檢查以確保參數既不是Ape也不是Human 但這超出了“純”Java 的能力。

我會讓你的hunt方法非靜態; 畢竟,這是對對象本身的操作。 然后,您可以為所有 Apes 定義行為:

public void hunt() {
    this.die(); // oh no!
}

當然,人類超越了這個基本想法:

@Override
public void hunt() {
    throw new IllegalMurderException("Human horn hunting is against intergalactic law");
}

添加對#isHuntable的檢查是可取的,因為與比喻保持一致,很高興知道在這樣做之前是否允許您尋找某些東西。 此外,我對人類狩獵有一個例外,因為它違反了您(好吧,我)期望#hunt實現的一般行為。

為了過度設計和 SOLID 的各種概念,您可以使用Huntable接口來實現這一點,它可以擴展一個Living接口:

public interface Living {
    public void die();
}

public interface Huntable extends Living {
    default public void hunt() {
        this.die();
    }
    //could use this for a generic exception in the interface too
    public boolean isHuntable(); 
}

public interface Ape extends Huntable {} // already Huntable

public interface Human extends Ape {
    //And finally, we break that behavioral contract like above
}

總的來說,這使您的泛型變得不必要。 如果您走那條路線,您將被迫進行類型轉換和手動檢查,如其他答案所示。 因此,雖然它回避了最初的問題,但我認為這是對潛在問題(XY 問題?)的更好解決方案。 更重要的是,關於人類的行為仍然屬於描述人類的一類。

編輯:

為了與靜態方法和檢查異常保持一致,雖然我強烈建議反對它(根據我的評論),但您可以使用方法重載,以便Human對象通過不同的簽名:

public static void hunt(Ape example) { ... }

public static void hunt(Human example) throws HuntingException {
    throw new HuntingException("cannot hunt humans");
}

HuntingException將擴展Exception而不是RuntimeException Exception / Error / Throwable對象,你讓自己本質上都是檢查的異常。 除此之外,作為開發人員自己不會產生編譯器錯誤。 您可以生成警告,但您會面臨相同的陷阱。

您可以理解為什么這很愚蠢,並且通過強制轉換,仍然可以在沒有編譯器錯誤的情況下對人類進行追捕:

Main.hunt((Ape) someHuman);

所以現在你回到在 #hunt 方法中添加類型檢查的兔子洞,它不會拋出編譯器錯誤(或者必須總是拋出一個錯誤)。 如果你總是拋出它,那么使用你的代碼(很可能你自己)的開發人員只會在每次調用 #hunt 時自動填充 try-catch 塊,只是為了處理Human的錯誤。 這增加了很多“代碼噪音”,只會讓事情變得混亂。

簡而言之,運行時異常是針對“獵殺人類”之類的開發人員錯誤的,從設計的角度來看,為什么將其作為靜態方法(更不用說主類)不好的原因太多了。

暫無
暫無

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

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