私は最初のファクトリデザインパターンを実装しようとしていますが、リストにファクトリで作成されたオブジェクトを追加するときにinstanceofの使用を避ける方法がわかりません。これは私がやろうとしていることです:
for (ABluePrint bp : bluePrints) {
AVehicle v = AVehicleFactory.buildVehicle(bp);
allVehicles.add(v);
// Can I accomplish this without using 'instanceof'?
if (v instanceof ACar) {
cars.add((ACar) v);
} else if (v instanceof ABoat) {
boats.add((ABoat) v);
} else if (v instanceof APlane) {
planes.add((APlane) v);
}
}
私がSOで読んだことから、「instanceof」を使用することはコードの匂いです。 「instanceof」を使用せずに工場で作成された車両のタイプを確認するより良い方法はありますか?
私はこれについて正しい方法で進んでいるかどうかさえ確信できないので、実装に関するフィードバック/提案を歓迎します。
以下の完全な例:
import Java.util.ArrayList;
class VehicleManager {
public static void main(String[] args) {
ArrayList<ABluePrint> bluePrints = new ArrayList<ABluePrint>();
ArrayList<AVehicle> allVehicles = new ArrayList<AVehicle>();
ArrayList<ACar> cars = new ArrayList<ACar>();
ArrayList<ABoat> boats = new ArrayList<ABoat>();
ArrayList<APlane> planes = new ArrayList<APlane>();
/*
* In my application I have to access the blueprints through an API
* b/c they have already been created and stored in a data file.
* I'm creating them here just for example.
*/
ABluePrint bp0 = new ABluePrint(0);
ABluePrint bp1 = new ABluePrint(1);
ABluePrint bp2 = new ABluePrint(2);
bluePrints.add(bp0);
bluePrints.add(bp1);
bluePrints.add(bp2);
for (ABluePrint bp : bluePrints) {
AVehicle v = AVehicleFactory.buildVehicle(bp);
allVehicles.add(v);
// Can I accomplish this without using 'instanceof'?
if (v instanceof ACar) {
cars.add((ACar) v);
} else if (v instanceof ABoat) {
boats.add((ABoat) v);
} else if (v instanceof APlane) {
planes.add((APlane) v);
}
}
System.out.println("All Vehicles:");
for (AVehicle v : allVehicles) {
System.out.println("Vehicle: " + v + ", maxSpeed: " + v.maxSpeed);
}
System.out.println("Cars:");
for (ACar c : cars) {
System.out.println("Car: " + c + ", numCylinders: " + c.numCylinders);
}
System.out.println("Boats:");
for (ABoat b : boats) {
System.out.println("Boat: " + b + ", numRudders: " + b.numRudders);
}
System.out.println("Planes:");
for (APlane p : planes) {
System.out.println("Plane: " + p + ", numPropellers: " + p.numPropellers);
}
}
}
class AVehicle {
double maxSpeed;
AVehicle(double maxSpeed) {
this.maxSpeed = maxSpeed;
}
}
class ACar extends AVehicle {
int numCylinders;
ACar(double maxSpeed, int numCylinders) {
super(maxSpeed);
this.numCylinders = numCylinders;
}
}
class ABoat extends AVehicle {
int numRudders;
ABoat(double maxSpeed, int numRudders) {
super(maxSpeed);
this.numRudders = numRudders;
}
}
class APlane extends AVehicle {
int numPropellers;
APlane(double maxSpeed, int numPropellers) {
super(maxSpeed);
this.numPropellers = numPropellers;
}
}
class AVehicleFactory {
public static AVehicle buildVehicle(ABluePrint blueprint) {
switch (blueprint.type) {
case 0:
return new ACar(100.0, 4);
case 1:
return new ABoat(65.0, 1);
case 2:
return new APlane(600.0, 2);
default:
return new AVehicle(0.0);
}
}
}
class ABluePrint {
int type; // 0 = car; // 1 = boat; // 2 = plane;
ABluePrint(int type) {
this.type = type;
}
}
Visitor pattern を実装できます。
詳細な回答
考え方は、 polymorphism を使用して型チェックを実行することです。各サブクラスは、スーパークラスで宣言する必要があるaccept(Visitor)
メソッドをオーバーライドします。次のような状況がある場合:
_void add(Vehicle vehicle) {
//what type is vehicle??
}
_
Vehicle
で宣言されたメソッドにオブジェクトを渡すことができます。 vehicle
の型がCar
で、_class Car
_がオブジェクトを渡したメソッドをオーバーライドした場合、そのオブジェクトはCar
クラスで宣言されたメソッド内で処理されます。これを利用して、Visitor
オブジェクトを作成し、オーバーライドされたメソッドに渡します。
_abstract class Vehicle {
public abstract void accept(AddToListVisitor visitor);
}
class Car extends Vehicle {
public void accept(AddToListVisitor visitor) {
//gets handled in this class
}
}
_
このVisitor
は、タイプCar
にアクセスするために準備する必要があります。 instanceof
を使用して実際のタイプを見つけることを避けたいタイプは、Visitor
で指定する必要があります。
_class AddToListVisitor {
public void visit(Car car) {
//now we know the type! do something...
}
public void visit(Plane plane) {
//now we know the type! do something...
}
}
_
ここで型チェックが行われます!
Car
がビジターを受け取ると、this
キーワードを使用して自身を渡す必要があります。クラスCar
にいるので、メソッドvisit(Car)
が呼び出されます。ビジターの内部で、オブジェクトのタイプがわかったので、目的のアクションを実行できます。
だから、上から:
必要なアクションを実行するVisitor
を作成します。ビジターは、アクションを実行するオブジェクトのタイプごとにvisit
メソッドで構成する必要があります。この場合、車両のビジターを作成しています:
_interface VehicleVisitor {
void visit(Car car);
void visit(Plane plane);
void visit(Boat boat);
}
_
実行するアクションは、車両を何かに追加することです。 AddTransportVisitor
;を作成します。交通機関の追加を管理する訪問者:
_class AddTransportVisitor implements VehicleVisitor {
public void visit(Car car) {
//add to car list
}
public void visit(Plane plane) {
//add to plane list
}
public void visit(Boat boat) {
//add to boat list
}
}
_
すべての車両は、車両の訪問者を受け入れることができる必要があります。
_abstract class Vehicle {
public abstract void accept(VehicleVisitor visitor);
}
_
訪問者が車両に渡されると、車両はvisit
メソッドを呼び出して、引数に自身を渡します。
_class Car extends Vehicle {
public void accept(VehicleVisitor visitor) {
visitor.visit(this);
}
}
class Boat extends Vehicle {
public void accept(VehicleVisitor visitor) {
visitor.visit(this);
}
}
class Plane extends Vehicle {
public void accept(VehicleVisitor visitor) {
visitor.visit(this);
}
}
_
そこで型チェックが行われます。正しいvisit
メソッドが呼び出されます。これには、メソッドのパラメーターに基づいて実行する正しいコードが含まれています。
最後の問題は、VehicleVisitor
がリストと対話することです。これがVehicleManager
の出番です。リストをカプセル化し、VehicleManager#add(Vehicle)
メソッドを介して車両を追加できます。
ビジターを作成するとき、マネージャーを(おそらくコンストラクターを介して)渡すことができるため、オブジェクトのタイプがわかったので、必要なアクションを実行できます。 VehicleManager
にはビジターが含まれ、VehicleManager#add(Vehicle)
呼び出しをインターセプトする必要があります。
_class VehicleManager {
private List<Car> carList = new ArrayList<>();
private List<Boat> boatList = new ArrayList<>();
private List<Plane> planeList = new ArrayList<>();
private AddTransportVisitor addVisitor = new AddTransportVisitor(this);
public void add(Vehicle vehicle) {
vehicle.accept(addVisitor);
}
public List<Car> getCarList() {
return carList;
}
public List<Boat> getBoatList() {
return boatList;
}
public List<Plane> getPlaneList() {
return planeList;
}
}
_
_AddTransportVisitor#visit
_メソッドの実装を作成できるようになりました。
_class AddTransportVisitor implements VehicleVisitor {
private VehicleManager manager;
public AddTransportVisitor(VehicleManager manager) {
this.manager = manager;
}
public void visit(Car car) {
manager.getCarList().add(car);
}
public void visit(Plane plane) {
manager.getPlaneList().add(plane);
}
public void visit(Boat boat) {
manager.getBoatList().add(boat);
}
}
_
ゲッターメソッドを削除し、車両のタイプごとにオーバーロードadd
メソッドを宣言することを強くお勧めします。これにより、manager.add(new Car())
のように、不要なときに「訪問」することによるオーバーヘッドが削減されます。
_class VehicleManager {
private List<Car> carList = new ArrayList<>();
private List<Boat> boatList = new ArrayList<>();
private List<Plane> planeList = new ArrayList<>();
private AddTransportVisitor addVisitor = new AddTransportVisitor(this);
public void add(Vehicle vehicle) {
vehicle.accept(addVisitor);
}
public void add(Car car) {
carList.add(car);
}
public void add(Boat boat) {
boatList.add(boat);
}
public void add(Plane plane) {
planeList.add(plane);
}
public void printAllVehicles() {
//loop through vehicles, print
}
}
class AddTransportVisitor implements VehicleVisitor {
private VehicleManager manager;
public AddTransportVisitor(VehicleManager manager) {
this.manager = manager;
}
public void visit(Car car) {
manager.add(car);
}
public void visit(Plane plane) {
manager.add(plane);
}
public void visit(Boat boat) {
manager.add(boat);
}
}
public class Main {
public static void main(String[] args) {
Vehicle[] vehicles = {
new Plane(),
new Car(),
new Car(),
new Car(),
new Boat(),
new Boat()
};
VehicleManager manager = new VehicleManager();
for(Vehicle vehicle : vehicles) {
manager.add(vehicle);
}
manager.printAllVehicles();
}
}
_
メソッドを車両クラスに追加して、テキストを印刷できます。次に、各専用のCarクラスのメソッドをオーバーライドします。次に、すべての車を車両リストに追加します。リストをループしてテキストを印刷します。
コードの再構築を行いました。それがあなたのために働くことを願っています。これをチェックして:
import Java.util.ArrayList;
class VehicleManager {
public static void main(String[] args) {
ArrayList<ABluePrint> bluePrints = new ArrayList<ABluePrint>();
ArrayList<AVehicle> allVehicles = new ArrayList<AVehicle>();
ArrayList<ACar> cars = null;
ArrayList<ABoat> boats = null;
ArrayList<APlane> planes = null;
/*
* In my application I have to access the blueprints through an API
* b/c they have already been created and stored in a data file.
* I'm creating them here just for example.
*/
ABluePrint bp0 = new ABluePrint(0);
ABluePrint bp1 = new ABluePrint(1);
ABluePrint bp2 = new ABluePrint(2);
bluePrints.add(bp0);
bluePrints.add(bp1);
bluePrints.add(bp2);
for (ABluePrint bp : bluePrints) {
AVehicle v = AVehicleFactory.buildVehicle(bp);
allVehicles.add(v);
// Can I accomplish this without using 'instanceof'?
// dont add objects to list here, do it from constructor or in factory
/*if (v instanceof ACar) {
cars.add((ACar) v);
} else if (v instanceof ABoat) {
boats.add((ABoat) v);
} else if (v instanceof APlane) {
planes.add((APlane) v);
}*/
}
cars = ACar.getCars();
boats = ABoat.getBoats();
planes = APlane.getPlanes();
System.out.println("All Vehicles:");
for (AVehicle v : allVehicles) {
System.out.println("Vehicle: " + v + ", maxSpeed: " + v.maxSpeed);
}
System.out.println("Cars:");
for (ACar c : cars) {
System.out.println("Car: " + c + ", numCylinders: " + c.numCylinders);
}
System.out.println("Boats:");
for (ABoat b : boats) {
System.out.println("Boat: " + b + ", numRudders: " + b.numRudders);
}
System.out.println("Planes:");
for (APlane p : planes) {
System.out.println("Plane: " + p + ", numPropellers: " + p.numPropellers);
}
}
}
class AVehicle {
double maxSpeed;
AVehicle(double maxSpeed) {
this.maxSpeed = maxSpeed;
}
void add(){}
}
class ACar extends AVehicle {
static ArrayList<ACar> cars = new ArrayList<ACar>();
int numCylinders;
ACar(double maxSpeed, int numCylinders) {
super(maxSpeed);
this.numCylinders = numCylinders;
}
void add(){
cars.add(this);
}
public static ArrayList<ACar> getCars(){
return cars;
}
}
class ABoat extends AVehicle {
static ArrayList<ABoat> boats = new ArrayList<ABoat>();
int numRudders;
ABoat(double maxSpeed, int numRudders) {
super(maxSpeed);
this.numRudders = numRudders;
}
void add(){
boats.add(this);
}
public static ArrayList<ABoat> getBoats(){
return boats;
}
}
class APlane extends AVehicle {
static ArrayList<APlane> planes = new ArrayList<APlane>();
int numPropellers;
APlane(double maxSpeed, int numPropellers) {
super(maxSpeed);
this.numPropellers = numPropellers;
}
void add(){
planes.add(this);
}
public static ArrayList<APlane> getPlanes(){
return planes;
}
}
class AVehicleFactory {
public static AVehicle buildVehicle(ABluePrint blueprint) {
AVehicle vehicle;
switch (blueprint.type) {
case 0:
vehicle = new ACar(100.0, 4);
break;
case 1:
vehicle = new ABoat(65.0, 1);
break;
case 2:
vehicle = new APlane(600.0, 2);
break;
default:
vehicle = new AVehicle(0.0);
}
vehicle.add();
return vehicle;
}
}
class ABluePrint {
int type; // 0 = car; // 1 = boat; // 2 = plane;
ABluePrint(int type) {
this.type = type;
}
}
上記のコードでは、クラスは追加する必要のあるコレクションについて知る必要があります。これは良いデザインの欠点と見なすことができ、受け入れられた回答に示されている訪問者デザインパターンを使用して克服できます( ファクトリーデザインパターンを実装するときに 'instanceof'を回避する方法? )。
そもそも、車、ボート、飛行機のリストにあまり満足していません。現実には複数の例がありますが、リストには本質的にすべてが含まれているわけではありません。工場が潜水艦やロケットの製造を開始するとどうなりますか?
代わりに、タイプcar、boat、planeの列挙型はどうでしょうか。車両のリストの配列があります。
汎用ビークルには、CatalogAsという抽象プロパティがあり、さまざまなビークルが実際にこれを実装し、適切な値を返します。
この質問が尋ねられてから長い時間が経っていることを知っています。見つけましたhttp://www.nurkiewicz.com/2013/09/instanceof-operator-and-visitor-pattern.htmlこれは便利そうです。誰かが興味を持っている場合に備えて、ここで共有してください。
AVehicleクラスが制御できない場合はどうなりますか?例えば。あなたはいくつかのサードパーティのライブラリからそれを持っていますか?したがって、Visitorパターンのaccept()メソッドを追加する方法はありません。また、おそらく各AVehicleサブクラスの定型コードを嫌い、すべてのクラスを1つの特別なクラスに入れてクラスをクリーンに保つこともできます。場合によっては、HashMapを使用した方が良い場合があります。
サンプルでは次を使用します。
Map<Class<? extends AVehicle>, List<? extends AVehicle>> lists = new HashMap<>();
lists.put(ACar.class, new ArrayList<ACar>());
lists.put(ABoat.class, new ArrayList<ABoat>());
lists.put(APlane.class, new ArrayList<APlane>());
for (ABluePrint bp : bluePrints) {
AVehicle v = AVehicleFactory.buildVehicle(bp);
allVehicles.add(v);
lists.get(v.getClass()).add(v);
}
このHashMapアプローチの問題は、すべての既知のサブクラスを含むすべての可能なクラスを登録する必要があることです。巨大な階層があり、タスクにすべてのクラスが必要でない場合でも、必要なものだけをマップに登録することで多くの作業を節約できます。