web-dev-qa-db-ja.com

工場設計パターンを実装するときに「instanceof」を回避する方法は?

私は最初のファクトリデザインパターンを実装しようとしていますが、リストにファクトリで作成されたオブジェクトを追加するときに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;
    }
}
34
Charlie James

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();
    }
}
_
65
Vince Emigh

メソッドを車両クラスに追加して、テキストを印刷できます。次に、各専用のCarクラスのメソッドをオーバーライドします。次に、すべての車を車両リストに追加します。リストをループしてテキストを印刷します。

2
Himanshu Ahire

コードの再構築を行いました。それがあなたのために働くことを願っています。これをチェックして:

    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'を回避する方法? )。

2
Anshuman

そもそも、車、ボート、飛行機のリストにあまり満足していません。現実には複数の例がありますが、リストには本質的にすべてが含まれているわけではありません。工場が潜水艦やロケットの製造を開始するとどうなりますか?

代わりに、タイプcar、boat、planeの列挙型はどうでしょうか。車両のリストの配列があります。

汎用ビークルには、CatalogAsという抽象プロパティがあり、さまざまなビークルが実際にこれを実装し、適切な値を返します。

2
Loren Pechtel

この質問が尋ねられてから長い時間が経っていることを知っています。見つけましたhttp://www.nurkiewicz.com/2013/09/instanceof-operator-and-visitor-pattern.htmlこれは便利そうです。誰かが興味を持っている場合に備えて、ここで共有してください。

1
Hari Rao

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アプローチの問題は、すべての既知のサブクラスを含むすべての可能なクラスを登録する必要があることです。巨大な階層があり、タスクにすべてのクラスが必要でない場合でも、必要なものだけをマップに登録することで多くの作業を節約できます。

0
engilyin

同様の問題があったため、このパターンを使用し、それをよりよく理解するために、コメント内の一連の内容を示す単純なUML図面を作成しました(数字に従ってください)。上記のVince Emighsソリューションを使用しました。パターンソリューションはよりエレガントですが、真に理解するのに時間がかかる場合があります。元のインターフェースよりも1つのインターフェースと1つのクラスが必要ですが、それらは非常に単純です。

the original is on the right side, the solution using the visitor pattern is on the left side

0
Orhan