概述

instanceof 是Java中的一个关键字,称为类型比较操作符,用于检查对象是否为指定Class的实例。在这个教程中,我们将探讨传统 instanceof 方法的替代方案,可能需要这些替代方法来改善代码设计和可读性。

instanceof 示例

首先我们回顾下 instanceof 的用法。先定义一个父类:

/**
 * 恐龙类
 */
public class Dinosaur {
}

接下来,创建第一子类:

/**
 * 鸭龙
 */
public class Anatotitan extends Dinosaur {
    String run() {
        return "running";
    }
}

创建第二个子类:

/**
 * 始盗龙
 */
public class Euraptor extends Dinosaur {    
    String flies() {
        return "flying";
    }
}

Dinosaur 类具有子类共有的其他方法,但为了简化,我们略过它们。现在,我们编写一个方法,用于创建对象实例并调用其run方法。在返回结果之前,我们会使用 instanceof 检查新实例的类型:

public static void moveDinosaur(Dinosaur dinosaur) {
    if (dinosaur instanceof Anatotitan) {
        Anatotitan anatotitan = (Anatotitan) dinosaur;
        anatotitan.run();
    } 
    else if (dinosaur instanceof Euraptor) {
        Euraptor euraptor = (Euraptor) dinosaur;
        euraptor.flies();
    }
}

下面我们将使用其他方案来替代 instanceof

使用 getClass()

*getClass()* 方法有助于获取对象的类。当我们检查一个对象是否属于某类时,可以使用 getClass() 作为 instanceof 的替代。在我们的示例设置中,保持父类和子类结构不变,然后编写一个测试方法,使用 getClass() 替换 instanceof

public static String moveDinosaurUsingGetClass(Dinosaur dinosaur) {
    if (dinosaur.getClass().equals(Anatotitan.class)) {
        Anatotitan anatotitan = (Anatotitan) dinosaur;
        return anatotitan.run();
    } else if (dinosaur.getClass().equals(Euraptor.class)) {
        Euraptor euraptor = (Euraptor) dinosaur;
        return euraptor.flies();
    }
    return "";
}

验证测试:

@Test
public void givenADinosaurSpecie_whenUsingGetClass_thenGetMovementOfEuraptor() {
    assertEquals("flying", moveDinosaurUsingGetClass(new Euraptor()));
}

使用多态

多态允许子类重写父类的方法。从而改进代码设计和可读性。由于我们知道所有恐龙都会移动,我们可以在父类中引入一个 move() 方法:

public class Dinosaur {    
    String move() {
        return "walking";
    } 
}

接着,修改子类,重写 move() 方法:

public class Anatotitan extends Dinosaur {
    @Override
    String move() {
        return "running";
    }
}
public class Euraptor extends Dinosaur {
    @Override
    String move() {
        return "flying";
    }
}

现在,我们可以直接引用子类,而无需使用 instanceof。编写一个接受父类作为参数的方法,根据物种返回恐龙的移动行为:

public static String moveDinosaurUsingPolymorphism(Dinosaur dinosaur) { 
    return dinosaur.move(); 
}

下面是测试:

@Test 
public void givenADinosaurSpecie_whenUsingPolymorphism_thenGetMovementOfAnatotitan() { 
    assertEquals("running", moveDinosaurUsingPolymorphism(new Anatotitan()));
}

实际项目中推荐使用这种方式。instanceof 的使用可能表示违反了 里氏替换原则(Liskov Substitution Principle)

使用枚举

枚举 类型中,变量可以定义为预定义常量的集合。我们可以利用这种方法改进我们的示例。首先,创建一个枚举,其中包含带方法的常量:

public enum DinosaurEnum {
    Anatotitan {
        @Override
        public String move() {
            return "running";
        }
    },
    Euraptor {
        @Override
        public String move() {
            return "flying";
        }
    };
    abstract String move();
}

枚举常量的作用类似于其他替代方案中的子类。接下来,修改 moveDinosaur() 方法以使用枚举类型:

public static String moveDinosaurUsingEnum(DinosaurEnum dinosaurEnum) {
    return dinosaurEnum.move();
}

测试:

@Test
public void givenADinosaurSpecie_whenUsingEnum_thenGetMovementOfEuraptor() {
    assertEquals("flying", moveDinosaurUsingEnum(DinosaurEnum.Euraptor));
}

这个设计消除了父类和子类。在复杂的场景中,当父类的行为比较多时,这种做法并不推荐。

使用Visitor设计模式

访问者模式有助于对相似或相关的对象进行操作,它将逻辑从对象类转移到另一个类。让我们将此方法应用于我们的示例设置。首先,创建一个带有方法的接口,并传入一个访问者作为参数,以便获取对象类型:

public interface Dinosaur {
    String move(Visitor visitor);
}

接下来,创建一个带有两个方法的访问者接口,方法接受子类作为参数:

public interface Visitor {
    String visit(Anatotitan anatotitan);
    String visit(Euraptor euraptor);
}

然后,让子类实现 Dinosaur 接口,并重写其方法。方法接受访问者作为参数,以获取对象类型,这将取代 instanceof 的使用:

public class Anatotitan implements Dinosaur {
    public String run() {
        return "running";
    }
    @Override
    public String move(Visitor dinoMove) {
        return dinoMove.visit(this);
    }
}

接下来,创建一个类来实现访问者接口并重写方法:

public class DinoVisitorImpl implements Visitor {
    @Override
    public String visit(Anatotitan anatotitan) {
        return anatotitan.run();
    }
    @Override
    public String visit(Euraptor euraptor) {
        return euraptor.flies();
    }
}

最后,为这种方法编写测试方法:

public static String moveDinosaurUsingVisitorPattern(Dinosaur dinosaur) {
    Visitor visitor = new DinoVisitorImpl();
    return dinosaur.move(visitor);
}

编写单元测试:

@Test
public void givenADinosaurSpecie_whenUsingVisitorPattern_thenGetMovementOfAnatotitan() {
    assertEquals("running", moveDinosaurUsingVisitorPattern(new Anatotitan()));
}

这种方法使用了一个接口。访问者包含了我们的程序逻辑。

总结

在这篇文章中,我们探讨了 instanceof 的不同替代方法。instanceof 方法可能违反了里氏替换原则。采用替代方案提供了更稳固的设计。多态方法是推荐的,因为它增加了更多的价值。如往常一样,完整的源代码可在 GitHub 查看。