“instanceof” 测试链¶
ID: java/chained-type-tests
Kind: problem
Security severity:
Severity: recommendation
Precision: high
Tags:
- maintainability
- language-features
Query suites:
- java-security-and-quality.qls
长序列的类型测试通常用于根据变量的类型将控制分派到代码的不同分支,如下例所示。它们通常用于模拟不支持模式匹配的语言中的模式匹配。虽然这可以用作调度方法,但也存在一些问题
它们难以维护。很容易添加新的子类型,并且忘记修改整个代码中的所有类型测试序列。
它们引入了对具体类的不必要的依赖关系。代码不能仅根据接口编写,而必须考虑所有不同的特殊情况。
它们容易出错 - 很容易在派生类型之前测试基类型,导致无法执行处理派生类型的代码。
建议¶
针对此问题,有许多不同的解决方案
**多态性**。您可以向类型层次结构添加虚拟方法,并将要调用的代码段放入每个具体类的相关重写中。在以下情况下,这是一个很好的解决方案:(a) 您可以更改类型层次结构,并且 (b) 正在实现的操作是类型应该实现的核心功能。如果您实现此解决方案,则必须小心不要引入不必要的依赖关系。如果操作依赖于自身依赖于类型层次结构的实体,那么您将无法在不创建依赖循环的情况下将操作移动到类型层次结构。
**访问者模式**。您可以引入一个访问者接口,其中包含针对类型层次结构中每种类型的访问方法,并在层次结构中的每种类型中添加一个
accept
方法,该方法将访问者作为其参数。accept
方法在this
上调用访问者的访问方法。然后,具体的访问者实现接口并根据需要处理每个特定类型。在以下情况下,这是一个很好的解决方案:(a) 您可以更改类型层次结构,并且 (b) 类型层次结构不应该知道正在实现的操作(为了避免依赖关系,或者因为它不是层次结构中类型的核心功能)。当您希望对同一组类型提供具有相同结构的多个操作,并且希望类型本身控制操作的结构方式时,它也很有用。例如,“使用顺序遍历访问此树,并将操作应用于每个节点”。基本的访问者模式并不适合所有情况,因为它具有循环依赖性,并且所涉及的基础结构相对较重。**反射**。您可以根据其中一个方法参数的类型查找一组重载方法,并手动调用该方法。这会导致类型安全性的损失,并且相当不整洁,但有时它是最佳解决方案。特别是,当您无法更改类型层次结构时(例如,因为它是第三方代码),反射非常有用。
示例¶
以下示例演示了“多态性”和“访问者模式”的使用。有关反射的更多详细信息,请参见 [Flanagan]。
import java.util.*;
public class ChainedInstanceof {
public static void main(String[] args) {
// BAD: example of a sequence of type tests
List<BadAnimal> badAnimals = new ArrayList<BadAnimal>();
badAnimals.add(new BadCat());
badAnimals.add(new BadDog());
for(BadAnimal a: badAnimals) {
if(a instanceof BadCat) System.out.println("Miaow!");
else if(a instanceof BadDog) System.out.println("Woof!");
else throw new RuntimeException("Oops!");
}
// GOOD: solution using polymorphism
List<PolymorphicAnimal> polymorphicAnimals = new ArrayList<PolymorphicAnimal>();
polymorphicAnimals.add(new PolymorphicCat());
polymorphicAnimals.add(new PolymorphicDog());
for(PolymorphicAnimal a: polymorphicAnimals) a.speak();
// GOOD: solution using the visitor pattern
List<VisitableAnimal> visitableAnimals = new ArrayList<VisitableAnimal>();
visitableAnimals.add(new VisitableCat());
visitableAnimals.add(new VisitableDog());
for(VisitableAnimal a: visitableAnimals) a.accept(new SpeakVisitor());
}
//#################### TYPES FOR BAD EXAMPLE ####################
private interface BadAnimal {}
private static class BadCat implements BadAnimal {}
private static class BadDog implements BadAnimal {}
//#################### TYPES FOR POLYMORPHIC EXAMPLE ####################
private interface PolymorphicAnimal {
void speak();
}
private static class PolymorphicCat implements PolymorphicAnimal {
public void speak() { System.out.println("Miaow!"); }
}
private static class PolymorphicDog implements PolymorphicAnimal {
public void speak() { System.out.println("Woof!"); }
}
//#################### TYPES FOR VISITOR EXAMPLE ####################
private interface Visitor {
void visit(VisitableCat c);
void visit(VisitableDog d);
}
private static class SpeakVisitor implements Visitor {
public void visit(VisitableCat c) { System.out.println("Miaow!"); }
public void visit(VisitableDog d) { System.out.println("Woof!"); }
}
private interface VisitableAnimal {
void accept(Visitor v);
}
private static class VisitableCat implements VisitableAnimal {
public void accept(Visitor v) { v.visit(this); }
}
private static class VisitableDog implements VisitableAnimal {
public void accept(Visitor v) { v.visit(this); }
}
}
参考文献¶
D. Flanagan, *Java in a Nutshell: A Desktop Quick Reference*。奥莱利媒体,1997 年。
E. Gamma、R. Helm、R. Johnson、J. Vlissides,*设计模式:可复用面向对象软件的要素*。Addison-Wesley Longman 出版社,Inc. 波士顿,马萨诸塞州,1995 年。