通常,当您迭代异构集合的项目时(在这种情况下,包含Rectangle
s 和Circle
s),您有两种选择:
- 仅处理父类型(此处为
Shape
)上可用的方法。
- 使用
instanceof
检查以不同方式处理每个子类型。
设计A:将所有功能放入Shape
通过这种设计,每个Shape
人都有责任知道如何打印自己的属性。这使用选项 1 - 循环代码永远不需要知道它是否具有 aRectangle
或 a Circle
。
,你Shape
会添加
abstract String getAttributesString();
Rectangle
将此实现为
@Override
String getAttributesString() {
return String.format("Rectangle {b=%f, h=%f}", b, h);
}
然后循环是
for (Shape shape : shapes) {
System.out.println(shape.getAttributesString());
}
您也可以覆盖toString()
on 方法Object
,但通常最好只toString
用于调试,而不是用于例如需要向用户显示的内容。类通常应该具有toString
打印实例数据的完整表示的覆盖。
设计 B:Instanceof 检查
这是选择 2。更改仅针对循环代码 - 它根本不需要修改形状。
for (Shape shape : shapes) {
if (shape instanceof Rectangle) {
Rectangle rectangle = (Rectangle) shape;
System.out.println(String.format("Rectangle {b=%f, h=%f}", rectangle.getB(), rectangle.getH()));
} else if (shape instanceof Circle) {
Circle circle = (Circle) shape;
System.out.println(String.format("Circle {r=%f}", circle.getR()));
}
}
设计C:访客模式
这是两种选择的混合。
interface ShapeVisitor {
void visitRectangle(Rectangle rectangle);
void visitCircle(Circle circle);
}
abstract class Shape {
void visit(ShapeVisitor visitor);
/* ... the rest of your code ... */
}
class Rectangle extends Shape {
@Override
void visit(ShapeVisitor visitor) {
visitor.visitRectangle(this);
}
/* ... the rest of your code ... */
}
class Circle extends Shape {
@Override
void visit(ShapeVisitor visitor) {
visitor.visitCircle(this);
}
/* ... the rest of your code ... */
}
然后循环看起来像:
for (Shape shape : shapes) {
shape.visit(new ShapeVisitor() {
@Override
void visitRectangle(Rectangle rectangle) {
System.out.println(String.format("Rectangle {b=%f, h=%f}", rectangle.getB(), rectangle.getH()));
}
@Override
void visitCircle(Circle circle) {
System.out.println(String.format("Circle {r=%f}", circle.getR()));
}
});
}
使用哪个?
设计 A 很好,因为它避免instanceof
和铸造,但缺点是您必须将打印逻辑放在形状类本身中,这会失去一些灵活性(如果您想在两种不同的情况下以不同的方式打印相同的形状列表怎么办? ?)。
设计 B 将打印逻辑放在您想要的位置,但是通过强制转换您无法充分利用编译器的类型检查。这很容易出错,因为例如,如果您要添加另一个Shape
子类型,您可能会忘记更新循环代码。
设计 C 结合了 A 和 B 的最佳特性。但问题是它不可扩展。如果您正在编写一个其他人将用来定义他们自己的Shape
子类型的库,他们就不能这样做,因为这需要修改ShapeVisitor
接口。但如果您的代码不需要以这种方式扩展,则它是合适的。
或者...
最终,所有这些在 Scala 中都变得更容易了。(是的,我意识到我不再回答你的问题,只是现在玩得开心。)
sealed trait Shape {
def color: String
def area: Double
}
case class Rectangle(b: Double, h: Double, color: String) extends Shape {
def area: Double = b*h
}
case class Circle(r: Double, color: String) extends Shape {
def area: Double = math.Pi*r*r
}
for (shape <- shapes) {
println(shape match {
case rectangle: Rectangle =>
"Rectangle {b=%f, h=%f}".format(rectangle.b, rectangle.h)
case circle: Circle =>
"Circle {r=%f}".format(circle.r)
})
}