可接受的instanceof使用

时间:2015-05-15 09:13:18

标签: java instanceof

我是Java新手并且在设计问题上苦苦挣扎。我知道使用instanceof可能表示存在设计缺陷,我理解通常给定的Animal/Dog/Cat类作为示例,将bark()meow()替换为makenoise()等。< / p>

我的问题是,如果我需要根据子类的类型调用没有相应方法的方法,那么什么是合理的设计?例如,如果我想要一个新方法biteleash(),如果该类是Dog,但如果它是Cat则什么也不做呢?

我确实考虑在biteleash()Animal没有做任何事情,并在Dog中覆盖它,但是有很多这样的方法,所以它似乎是一个笨重的解决方案。类似地,如果调用者需要根据它所拥有的子类做一些不同的事情,例如。如果子类是Cat,则终止? instanceof在这里可以接受,还是有更好的方式?

public class Animal {

    String name;

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void makeNoise() {
        System.out.println("Some noise for a generic animal!");
    }

}

public class Cat extends Animal {

    public Cat(String name) {
        super(name);
    }

    @Override
    public void makeNoise() {
        System.out.println("Meow");
    }
}

public class Dog extends Animal {

    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeNoise() {
        System.out.println("Woof");
    }

    public void biteLeash() {
        System.out.println("Leash snapped!");
    }
}

import java.util.Random;

public class CodeExample {


    public static void main(String[] args) {

        Animal animal = getSomeAnimal();
        System.out.println("My pet is called " + animal.getName());
        animal.makeNoise();

        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.biteLeash();
            // do lots of other things because animal is a dog
            // eg. sign up for puppy training lessons
        }
    }

    private static Animal getSomeAnimal() {
        Animal animal;
        Random randomGenerator = new Random();
        int randomInt = randomGenerator.nextInt(100);      
        if (randomInt < 50) {
            animal = new Dog("Rover");
        }
        else {
            animal = new Cat("Tiddles");
        }
        return animal;
    }
}

9 个答案:

答案 0 :(得分:10)

撰写将在这里为您提供帮助,并且在Java中是惯用的。

设计一个名为Leashable接口。这是由Dog实现的,而不是Cat

您可以尝试对instanceof进行引用,以查看它是否由您的特定对象实现,而不是使用Leashable

在我看来,你应该继续以类似的方式:建立一个NoisyAnimal接口。也许只是Noisy为什么噪音只与动物相关?例如,为Parrot实施该问题将会产生超出CatDog的技术挑战。良好的可维护程序可以隔离复杂性和组合区域,帮助您实现这一目标。

答案 1 :(得分:5)

你不应该使用具体的课程。实例本身不是问题。它存在是有原因的。您应该使用松散耦合的接口,即您的代码不应该依赖于具体的类实现。我建议你尽可能使用接口(即IAnimal而不是Animal)

不应该检查Dog,你应该使用像ILeashable这样的界面(对于名字来说有点荒谬),然后:

public interface ILeashable {
   //add other methods which is connected to being on a leash
   void biteLeash();
}

class Dog implements ILeashable {...}

也没有一种方法可以做到这一点,有一些模式,即装饰器或依赖倒置,在这种情况下可能对你有帮助。

答案 2 :(得分:1)

您知道,您遇到的这个问题并不是您在现实世界中普遍面临的问题。如果必须在实现接口或抽象基类的某个类上具有特定于实现的逻辑,那么通常是因为在某个更高级别需要获取派生属性。这个伪代码来说明:

interface ISellable {
     decimal getPrice();
}
class CaseItem : ISellable {
    int numItemsInCase;
    decimal pricePerUnit;
    decimal getPrice() {
         return numItemsInCase*pricePerUnit;
    }
}
class IndividualItem : ISellable{
    decimal pricePerUnit;
    decimal getPrice() {
         return pricePerUnit;
    }
}
main() {
    aCaseItem = new CaseItem { pricePerUnit = 2, numItemsInCase=5 }; //getPrice() returns 10
    anIndividualItem = new IndividualItem { pricePerUnit = 5 }; //getPrice() returns 5

    List<ISellable> order = new List<ISellable>();
    order.Add(aCaseItem);
    order.Add(anIndividualItem);

    print getOrderTotal(order);
}
function getOrderTotal(List<ISellable> sellableItems) {
    return sellableItems.Sum(i => i.getPrice());
}

请注意,我使用界面抽象出项目价格的概念,但是当我实际上在主方法中时,我可以轻松地创建特定类型的实例以便控制这两个班的行为。

然而,当我需要得到价格时,我将这些项目作为ISellable列表引用,这只会暴露他们的&#34; getPrice()&#34;方便我的方法。

就个人而言,我一直认为动物的情况严重缺乏。它没有以合理的方式解释这个概念,也没有提示如何在现实世界中使用它。

答案 3 :(得分:1)

以这种方式思考:什么事件导致Dog咬住它的皮带?或者换句话说,你有什么动机让它执行这个动作?

在你的例子中实际上没有。事实上,在您的主要方法中,您决定进行检查,如果您随机创建的动物是Dog,您可以使它做某些狗的事情。这不是现实世界中的代码如何运作。

编写代码时,需要解决一些问题。为了坚持动物的例子,让我们假装你写一个游戏,你有宠物对某些事件作出反应,比如开启真空或吃点东西。仅从这句话中我们就可以创建一个合理的类层次结构:

interface Animal {

    void reactToVacuum();
    void receiveTreat();

}

class Dog implements Animal {

    public void biteLeash() {
        System.out.println("Leash snapped!");
    }

    public void wiggleTail() {
        System.out.println("Tail is wiggling!");
    }

    @Override
    public void reactToVacuum() {
        biteLeash();
    }

    @Override
    public void receiveTreat() {
        wiggleTail();
    }
}

正如你所看到的,皮带咬合是为了响应一个事件,即打开真空。

有关更实际的示例,请使用Android的视图层次结构。 View是屏幕上每个控件的基类,例如一个Button,一个EditText等等。 View.onDraw()是为每个视图定义的,但根据您拥有的视图,会发生不同的事情。例如EditText会执行类似drawCursor()drawText()的内容。

正如您所看到的,问题的答案&#34;什么事件导致EditText绘制光标&#34;是&#34;需要在屏幕上绘制&#34;。不需要instanceof或条件检查。

答案 4 :(得分:0)

如您的示例所示 - 某些子类可以有不同的方法 这是您必须使用instanceof的典型情况。

答案 5 :(得分:0)

如果您的子类已修复,那么您可以使用animalType来避免instanceof。

我更喜欢你可以使用接口来检查该子类是否可以使用该功能,但是对于大量的子类依赖方法它是不可行的。 (由user2710256给出的解决方案。)

Enum Type {
        DOG,CAT ;
    }

    public abstract class Animal {

        String name;

        Type type;

        public Animal(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void makeNoise() {
            System.out.println("Some noise for a generic animal!");
        }

        abstract Type getType();

    }

    public class Cat extends Animal {

        public Cat(String name) {
            super(name);
        }

        @Override
        public void makeNoise() {
            System.out.println("Meow");
        }

        public Type getType() {
            return Type.CAT;
        }
    }

    public class Dog extends Animal {


        public Dog(String name) {
            super(name);
        }

        @Override
        public void makeNoise() {
            System.out.println("Woof");
        }

        public void biteLeash() {
            System.out.println("Leash snapped!");
        }

        public Type getType(){
            return Type.DOG;
        }
    }

    import java.util.Random;

    public class CodeExample {


        public static void main(String[] args) {

            Animal animal = getSomeAnimal();
            System.out.println("My pet is called " + animal.getName());
            animal.makeNoise();

            switch(animal.getType())
            {
               case DOG:  
                        {
                        Dog dog = (Dog) animal;
                        dog.biteLeash();
                        // do lots of other things because animal is a dog
                        // eg. sign up for puppy training lessons
                        }

                case CAT:
                        {
                            // do cat stuff
                        }
                default: 
                        throw new Exception("Invalid Animal");
            }
        }

        private static Animal getSomeAnimal() {
            Animal animal;
            Random randomGenerator = new Random();
            int randomInt = randomGenerator.nextInt(100);      
            if (randomInt < 50) {
                animal = new Dog("Rover");
            }
            else {
                animal = new Cat("Tiddles");
            }
            return animal;
        }
    }

答案 6 :(得分:0)

当然biteleash()中有Cat没有意义,因为你不会用皮带牵着猫。因此Animal不应该biteleash()。也许您应该在playWith()中添加Animal之类的方法,其中Dogbiteleash()。我想说的是你可能需要更一般。您不应该关心手动拨打biteleash()是否是狗。向动物园添加Ferret需要添加动物为Ferret的可能性,因为它们也可以biteleash()

也许是一种方法shouldTerminate(),它会在true中返回Cat。您可能希望暂时终止Tortoise

通常,人们认为这是不好的做法,如果添加新的子类需要使用此类更改代码。

答案 7 :(得分:0)

如果可以的话,这是一种非常不切实际的方法,我不确定你的目标是什么,但是实例会很乏味,你最终得到的是高耦合,这是不可取的。做OOD。 这就是我要做的事情:

首先,摆脱类Animal并使其成为如下界面

public interface Animal {

    public String getName();

    public void makeNoise();

    public void performAggression();
}

然后在狗:

public class Dog implements Animal {

    private String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void makeNoise() {
        System.out.println("Woof");
    }

    @Override
    public void performAggression(){
        biteLeash();
    }

    private void biteLeash() {
        System.out.println("Leash snapped!");
    }
}

最后,猫:

public class Cat implements Animal {

    private String name;

    public cat(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void makeNoise() {
        System.out.println("Meow");
    }

    @Override
    public void performAggression(){
        hiss();
    }

    private void hiss() {
        System.out.println("Hisssssss");
    }
}

这样你就可以在主要课程中做到如下:

public class test{

    public test(){
        dog = new Dog("King");
        cat = new Cat("MissPrincess");
    }

    public void performAggression(Animal animal){
        animal.performAggression();
    }

你得到的奖励是,你可以将你想要的任何类传递给一个方法,只要它们实现相同的接口,如上面方法performAggression(Animal animal)中的测试类所示

所有测试类需要知道的是那3个方法,其他所有方法都可以在相应的类中内部完成,而测试类不需要了解它,因此&#34; private&#34; biteLeash()hiss()方法的可见性。

您最终会得到一个非常低的耦合,以后可以轻松编辑。

通过这样做,你也可以有效地实现高凝聚力,因为你不必长时间参与/ ifelse / ifelse ......(等等)来确定你正在处理什么类的课程。 / p>

答案 8 :(得分:0)

运营商instanceof只有在应用于接口时才能很好地扩展。原因是接口将您的域分为两部分:一些对象是实现接口的类的实例,而其他对象则不是。引入新类很可能无法打破依赖if (x instanceof ISomething)的当前算法,因为新类将实现ISomething或不实现if。如果是这样,x正文应该ISomething投射到if并充分利用它;如果没有,那么x的主体可能对instanceof不感兴趣。

这与Seal应用于类的方法不同。例如,海豹会吠叫,但它们不是狗:你不能从Dog继承instanceof或反之。因此,正如您所暗示的那样,您知道如果您使用x来检查是否可以if (x instanceof Dog) { ((Dog)x).bark(); } else if (x instanceof Seal) { ((Seal)x).bark(); } 吠叫,那么您最终可能会遭受这样的可憎行为:

ICanBark

解决方案是使用接口if (x instanceof ICanBark) { ((ICanBark)x).bark(); } 并对其进行测试:

ICanMeow

如果添加更多动物,无论它们是否可以吠叫,这都可以很好地扩展。但是如果你想增加更多的噪音,它就不能很好地扩展,因为你可能想要引入更多的接口,比如ICanBark等。

解决方案是避免处于这种情况; ICanMeowIMakeNoise显然是相同的行为Dog,因此CatSealinstanceof实施了单一界面。

在多态性的背景下,我不喜欢动物/狗/猫/噪音的例子。相反,我将向您展示我之前正在研究的组件系统的真实世界示例。在组件系统中,组件安装在实体上,以便为其提供其他行为。例如,视频游戏中的怪物是安装了某些组件的实体(这些东西的灵感来自Unity 5引擎):

  1. IAudioSource
  2. IRenderer
  3. IMovementAI
  4. ILiving
  5. 可能实现这些行为的四个类可能是:

    1. ScaryGruntingSound
    2. MeshOpenGLRender
    3. StupidQuadrupedAI
    4. Armorless
    5. 如果你有一个实体集合,那么如果你为接口for (Entity e : entities) { for (Component c : e.getComponents()) { if (c instanceof IAudioSource) { ((IAudioSource)c).play(); } ... if (c instanceof IUserInput) { ((IUserInput)c).poll(); } } } 那么它完全可以接受(并且它会很好地扩展):

      c instanceof IUserInput

      由于怪物不受玩家控制,instanceof失败,每个人都很开心。使用ICanBark/ICanMeow岩石和IAudioSource/IRenderer/IMovementAI/ILiving糟透了的原因是因为ICanBark/ICanMeow是怪物意味着四个完全不相关的方面,而IMakeNoise是两个表现形式相同方面(并且应该通过将它们合并到I did consider having biteleash() in Animal which does nothing, and overriding it in Dog )来处理。

      修改

      text

      这是一个笨重的解决方案,我相信它只能出于性能原因。如果我没弄错的话,Swing for Java会偶尔使用这种模式。