一般に、異種コレクション (この場合は と の両方を含む) のアイテムを反復処理する場合、Rectangle
次Circle
の 2 つの選択肢があります。
- 親型 (ここでは ) で使用可能なメソッドのみを扱います
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: インスタンスのチェック
これは選択肢 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:来客パターン
これは、2 つの選択肢の組み合わせのようなものです。
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
いましたが、欠点は、印刷ロジックを形状クラス自体に配置する必要があり、柔軟性が失われることでした (2 つの異なる状況で同じ形状のリストを異なる方法で出力したい場合はどうなるでしょうか)。 ?)。
設計 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)
})
}