簡體   English   中英

合理的`instanceof`? 將其與接口而不是實現類型一起使用

[英]Justified `instanceof`? Using it with an interface but not an implementation type

當代碼中包含 Java instanceof運算符時,許多人會揚起眉毛說這是一個禁忌。 例如,在另一個 SO Q&A中,答案是:

請注意,如果您必須經常使用該運算符,則通常暗示您的設計存在一些缺陷。 因此,在設計良好的應用程序中,您應該盡可能少地使用該運算符(當然,該一般規則也有例外)。

但是,它沒有進一步詳細說明何時使用instanceof可以,何時不可以。

我對此進行了一些思考,並闡明了以下指導方針。 我以為這可能已經在 Internet 上的某個地方討論過,但我找不到它。 因此,這個問題並征求您的意見:

在接口上使用instanceof是可以的; 在實現上使用instanceof是不行的

這是一個關於“好的”案例的例子。

示例:動物目錄,其中一些(但不是全部)可以飛行

Animal.java

public interface Animal {
    String getName();
    String makeNoise();
}

CanFly.java

public interface CanFly {
    float getMaxInAirDistanceKm();
}

Cat.java

public class Cat implements Animal {
    @Override
    public String getName() {
        return "Cat";
    }

    @Override
    public String makeNoise() {
        return "meow";
    }
}

BaldEgale.java

public class BaldEagle implements Animal, CanFly {
    @Override
    public String getName() {
        return "BaldEagle";
    }

    @Override
    public String makeNoise() {
        return "whistle";
    }

    @Override
    public float getMaxInAirDistanceKm() {
        return 50;
    }
}

Catalog.java

import java.util.ArrayList;
import java.util.List;

public class Catalog {
    private List<Animal> animals = new ArrayList<>();

    public void putAnimal(Animal animal) {
        animals.add(animal);
    }

    public void showList() {
        animals.forEach(animal -> {
            StringBuilder sb = new StringBuilder();
            sb.append(animal.getName() + ": ");
            sb.append(animal.makeNoise() + " ");

            // this block exemplifies some processing that is 
            //   specific to CanFly animals
            if (animal instanceof CanFly) {
                sb.append(String.format(" (can stay in air for %s km)",
                        ((CanFly) animal).getMaxInAirDistanceKm()));
            }
            System.out.println(sb.toString());
        });
    }

    public static void main(String[] args){

        Catalog catalog = new Catalog();
        Cat cat = new Cat();
        BaldEagle baldEagle = new BaldEagle();
        catalog.putAnimal(cat);
        catalog.putAnimal(baldEagle);

        catalog.showList();
    }
}

測試 Output

Cat: meow 
BaldEagle: whistle  (can stay in air for 50.0 km)

更新於 2019-10-09添加“不正常”案例的示例:

我們可以刪除CanFly接口,在showList()方法中,我們將instanceof應用到具體實現BaldEagle上——就像這樣:

    public void showList() {
        animals.forEach(animal -> {
            StringBuilder sb = new StringBuilder();
            sb.append(animal.getName() + ": ");
            sb.append(animal.makeNoise() + " ");

            if (animal instanceof BaldEagle) {
                sb.append(String.format(" (can stay in air for %s km)",
                        ((BaldEagle) animal).getMaxInAirDistanceKm()));
            }
            System.out.println(sb.toString());
        });
    }

這種方法不行,因為代碼現在依賴於實現,而不是接口。 例如,它可以防止換出代表 Bald Eagle 的另一個實現(例如BaldEagleImpl

我認為人們認為總是有一個“更清潔”的解決方案來產生你想要的那種行為。

在您的示例中,我會說訪問者設計模式的使用在不使用 instanceOf 的情況下完全相同:

public interface Animal {
    String getName();
    String makeNoise();
    void accept(AnimalVisitor v);
}

public interface AnimalVisitor() {
    void visit(Cat a);
    void visit(BaldEagle a);
}

public interface CanFly {
    float getMaxInAirDistanceKm();
}

public class Cat implements Animal {
    void accept(Visitor v) {
        v.visit(this);
    }
}

public class BaldEagle implements Animal, CanFly {
    void accept(Visitor v) {
        v.visit(this);
    }
}

public class DisplayVisitor implements AnimalVisitor  {
    void visit(Cat a) {
       //build & display your string
    }

    void visit(BaldEagle a) {
       //build & display your string
    }
}

public class Catalog {
    private List<Animal> animals = new ArrayList<>();

    public void putAnimal(Animal animal) {
        animals.add(animal);
    }

    public void showList() {
        DisplayVisitor display = new DisplayVisitor();
        animals.forEach(a->a.accept(display));
    }
}

雖然我沒有完全回答您的問題,但它表明,在大多數情況下,無需使用instanceOf即可完成相同的行為,只需以 OOP 的方式思考並使用已知模式即可。

但是,當使用instanceof沒問題的時候就不再詳細說明了

這不是您問題的直接答案,但我想說instanceof僅在所有其他選項都不可行時才合適。

在接口上使用instanceof是可以的; 在實現上使用instanceof是不行的

我將重新表述為“在接口上使用instanceof不如在實現上使用instanceof糟糕”,但這只是強耦合不好的一般規則的必然結果。 通常有更好的選擇。

當您想使用instanceof時,您應該首先考慮引入額外的接口或接口方法或使用訪問者模式(參見其他答案)。 所有這些選項都是在 Java 中實現所需行為的更簡潔的方法。

這並不總是優雅的,並且可能需要人工接口或導致接口膨脹,這就是為什么其他一些語言支持臨時聯合類型和代數數據類型的原因。 但是instanceof也不是模擬的好方法,因為 Java 的類型系統無法幫助您確保處理所有可能的選項。

首先,重要的是要注意面向對象的編程范式是對諸如instanceof之類的類型檢查的阻力的根源。 其他范例不一定有這種阻力,甚至可能鼓勵類型檢查。 所以這個問題只有在你嘗試做 OOP 時才真正有意義。

如果您嘗試執行 OOP,那么您應該盡可能多地利用多態性。 多態性是 OOP 的主要武器。 類型檢查是多態性的對立面。

當然,抽象的類型檢查優於具體實現的類型檢查; 但這只是重申依賴倒置原則(依賴於抽象,而不是具體化)。

在 OOP 中,每次使用類型檢查都可以被視為錯失多態性的機會。

暫無
暫無

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

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