it-swarm.com.de

Wie kann "instanceof" vermieden werden, wenn das Factory Design Pattern implementiert wird?

Ich versuche, mein erstes Factory Design Pattern zu implementieren, und ich weiß nicht, wie ich es vermeiden kann, instanceof zu verwenden, wenn Sie die werksseitigen Objekte zu Listen hinzufügen. Das versuche ich zu tun:

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);
    }
}

Nach dem, was ich über SO gelesen habe, ist die Verwendung von 'instanceof' ein Codegeruch. Gibt es eine bessere Möglichkeit, den vom Hersteller erstellten Fahrzeugtyp zu prüfen, ohne 'instanceof' zu verwenden?

Ich freue mich über Rückmeldungen/Vorschläge zu meiner Implementierung, da ich nicht einmal sicher bin, ob ich das richtig mache.

Vollständiges Beispiel unten:

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;
    }
}
33
Charlie James

Sie können das Besuchermuster implementieren.


Detaillierte Antwort

Die Idee ist, Polymorphism zu verwenden, um die Typprüfung durchzuführen. Jede Unterklasse überschreibt die accept(Visitor)-Methode, die in der Superklasse deklariert werden sollte. Wenn wir eine Situation haben wie:

void add(Vehicle vehicle) {
    //what type is vehicle??
}

Wir können ein Objekt an eine in Vehicle deklarierte Methode übergeben. Wenn vehicle vom Typ Car ist und die Methode, an die wir das Objekt übergeben haben, durch class Car überschrieben wird, wird dieses Objekt jetzt in der in der Car-Klasse deklarierten Methode verarbeitet. Wir nutzen dies zu unserem Vorteil: Erstellen eines Visitor-Objekts und übergeben Sie es an eine überschreibende Methode:

abstract class Vehicle {
    public abstract void accept(AddToListVisitor visitor);
}

class Car extends Vehicle {
    public void accept(AddToListVisitor visitor) {
        //gets handled in this class
    }
}

Dieses Visitor sollte für den Besuch des Typs Car vorbereitet sein. Jeder Typ, den Sie vermeiden möchten, instanceof zum Ermitteln des tatsächlichen Typs zu verwenden, muss in Visitor angegeben werden. 

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...
    }
}

Hier passiert die Typprüfung!

Wenn Car den Besucher empfängt, sollte er sich selbst mit dem this-Schlüsselwort übergeben. Da wir uns in der Klasse Car befinden, wird die Methode visit(Car) aufgerufen. Innerhalb unseres Besuchers können wir die gewünschte Aktion ausführen, nachdem wir den Typ des Objekts kennen.


Also von oben:

Sie erstellen eine Visitor, die die gewünschten Aktionen ausführt. Ein Besucher sollte aus einer visit-Methode für jeden Objekttyp bestehen, für den Sie eine Aktion ausführen möchten. In diesem Fall erstellen wir einen Besucher für Fahrzeuge:

interface VehicleVisitor {
    void visit(Car car);
    void visit(Plane plane);
    void visit(Boat boat);
}

Die Aktion, die wir ausführen möchten, ist das Hinzufügen des Fahrzeugs zu etwas. Wir würden eine AddTransportVisitor erstellen; Ein Besucher, der das Hinzufügen von Transporten verwaltet:

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
    }
}

Jedes Fahrzeug sollte Fahrzeugbesucher akzeptieren können:

abstract class Vehicle {
    public abstract void accept(VehicleVisitor visitor);
}

Wenn ein Besucher an ein Fahrzeug übergeben wird, sollte das Fahrzeug seine visit-Methode aufrufen und sich in die Argumente übergeben:

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);
    }
}

Dort passiert die Typenprüfung. Die korrekte visit-Methode wird aufgerufen, die den korrekten Code enthält, der basierend auf den Parametern der Methode ausgeführt werden soll.

Das letzte Problem ist, dass VehicleVisitor mit den Listen interagiert. Hier kommt Ihr VehicleManager ins Spiel: Es kapselt die Listen, sodass Sie Fahrzeuge über eine VehicleManager#add(Vehicle)-Methode hinzufügen können.

Wenn wir den Besucher erstellen, können wir den Manager an ihn übergeben (möglicherweise über den Konstruktor), sodass wir die gewünschte Aktion ausführen können, sobald wir den Objekttyp kennen. Die VehicleManager sollte die Besucher- und Intercept VehicleManager#add(Vehicle)-Aufrufe enthalten:

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;
    }
}

Wir können jetzt Implementierungen für die AddTransportVisitor#visit Methoden schreiben:

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);
    }
}

Ich empfehle dringend, die Getter-Methoden zu entfernen und überladene add-Methoden für jeden Fahrzeugtyp zu deklarieren. Dies reduziert den Aufwand für "Besuche", wenn dies nicht erforderlich ist, z. B. 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

Sie können der Fahrzeugklasse eine Methode hinzufügen, um den Text zu drucken. Überschreiben Sie dann die Methode in jeder spezialisierten Klasse. Dann fügen Sie einfach alle Autos der Fahrzeugliste hinzu. Und die Liste durchlaufen, um den Text zu drucken.

2
Himanshu Ahire

Führen Sie eine Umstrukturierung Ihres Codes durch. Hoffe das funktioniert für dich. Überprüfen Sie dies:

    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;
        }
    }

Mit dem obigen Code muss die Klasse die Sammlung kennen, zu der sie hinzugefügt werden muss. Dies kann als Nachteil für ein gutes Design betrachtet werden und kann mithilfe des Besucherdesignmusters überwunden werden, wie in der akzeptierten Antwort gezeigt wird ( Wie vermeide ich "instanceof" beim Implementieren eines Fabrikdesignmusters? ).

2
Anshuman

Ich bin mit den Listen von Autos, Booten und Flugzeugen überhaupt nicht sehr zufrieden. Sie haben mehrere Beispiele für die Realität, aber die Liste ist nicht von Natur aus alles - was passiert, wenn Ihre Fabrik U-Boote oder Raketen baut?

Wie wäre es stattdessen mit einem Enum mit den Typen Auto, Boot und Flugzeug. Sie haben eine Reihe von Fahrzeuglisten.

Das generische Fahrzeug hat eine abstrakte Eigenschaft CatalogAs. Die verschiedenen Fahrzeuge implementieren dies tatsächlich und geben den richtigen Wert zurück.

2
Loren Pechtel

Ich weiß, es ist lange her, seit diese Frage gestellt wurde. Ich habe http://www.nurkiewicz.com/2013/09/instanceof-operator-and-visitor-pattern.html gefunden, was nützlich zu sein scheint. Teilen Sie es hier, falls jemand interessiert ist. 

1
Hari Rao

Was ist, wenn AV-Fahrzeugklassen außerhalb Ihrer Kontrolle liegen? Z.B. Sie haben es von einem Drittanbieter lib? Sie haben also keine Möglichkeit, die Visitor-Muster-Accept-Methode hinzuzufügen. Außerdem können Sie Boilerplate-Code in jeder der AVehicle-Unterklassen möglicherweise nicht mögen und es vorziehen, alles in einer speziellen Klasse zusammenzufassen, um Ihre Klassen sauber zu halten.

Verwenden Sie in Ihrer Probe einfach:

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);
}

Das Problem bei diesem HashMap-Ansatz besteht darin, dass Sie alle möglichen Klassen einschließlich aller bekannten Unterklassen registrieren müssen. Wenn Sie jedoch über eine große Hierarchie verfügen und nicht alle Klassen für Ihre Aufgabe benötigt werden, können Sie sich viel Arbeit sparen, indem Sie sich in der Map nur die benötigten registrieren. 

0
engilyin

Ich hatte ein ähnliches Problem, also habe ich dieses Muster verwendet, um es besser zu verstehen, habe ich eine einfache UML-Zeichnung erstellt, die die Reihenfolge der Dinge in Kommentaren zeigt (folgen Sie den Zahlen). Ich habe die Vince Emighs-Lösung oben verwendet. Die Musterlösung ist eleganter, kann jedoch einige Zeit dauern, um wirklich zu verstehen. Es erfordert eine Schnittstelle und eine Klasse mehr als das Original, aber sie sind sehr einfach.

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

0
Orhan