web-dev-qa-db-fra.com

Comment éviter "l'instance" lors de la mise en œuvre du modèle de conception d'usine?

J'essaie d'implémenter mon premier modèle de conception d'usine et je ne sais pas comment éviter d'utiliser instance of lorsque j'ajoute les objets fabriqués en usine aux listes. C'est ce que j'essaie de faire:

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

D'après ce que j'ai lu sur SO, utiliser 'instanceof' est une odeur de code. Existe-t-il un meilleur moyen de vérifier le type de véhicule créé par l'usine sans utiliser «instanceof»?

Je me réjouis de tout commentaire/suggestion concernant ma mise en œuvre, car je ne suis même pas sûr d’agir de la bonne façon.

Exemple complet ci-dessous:

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

Vous pouvez implémenter le modèle Visiteur .


Réponse détaillée

L'idée est d'utiliser polymorphisme pour effectuer la vérification de type. Chaque sous-classe remplace la méthode accept(Visitor), qui doit être déclarée dans la superclasse. Quand nous avons une situation comme:

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

Nous pouvons passer un objet dans une méthode déclarée dans Vehicle. Si vehicle est de type Car et que class Car substitue la méthode dans laquelle nous avons passé l'objet, cet objet serait maintenant traité dans la méthode déclarée dans la classe Car. Nous utilisons ceci à notre avantage: créer un objet Visitor et le transmettre à une méthode surchargée:

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

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

Cette Visitor devrait être prête à visiter le type Car. Tout type que vous souhaitez éviter d'utiliser instanceof pour rechercher le type réel doit être spécifié dans 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...
    }
}

Voici où la vérification de type se passe!

Lorsque la variable Car reçoit le visiteur, elle doit utiliser le mot clé this. Puisque nous sommes dans la classe Car, la méthode visit(Car) sera invoquée. À l'intérieur de notre visiteur, nous pouvons effectuer l'action que nous voulons, maintenant que nous connaissons le type de l'objet.


Donc, du haut:

Vous créez une Visitor, qui effectue les actions souhaitées. Un visiteur doit consister en une méthode visit pour chaque type d'objet sur lequel vous souhaitez effectuer une action. Dans ce cas, nous créons un visiteur pour les véhicules:

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

L'action que nous voulons effectuer consiste à ajouter le véhicule à quelque chose. Nous créerions une AddTransportVisitor; un visiteur qui gère l'ajout de transports:

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

Chaque véhicule devrait pouvoir accepter les visiteurs:

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

Lorsqu'un visiteur est transmis à un véhicule, celui-ci doit invoquer sa méthode visit en se transmettant lui-même dans les arguments:

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

C'est là que la vérification de type a lieu. La méthode visit correcte est appelée et contient le code correct à exécuter en fonction des paramètres de la méthode.

Le dernier problème est d'avoir la VehicleVisitor interagir avec les listes. C'est là que votre VehicleManager entre: il encapsule les listes, vous permettant d'ajouter des véhicules via une méthode VehicleManager#add(Vehicle).

Lorsque nous créons le visiteur, nous pouvons lui transmettre le gestionnaire (éventuellement via son constructeur), afin que nous puissions exécuter l'action souhaitée, maintenant que nous connaissons le type de l'objet. La VehicleManager doit contenir le visiteur et intercepter les appels 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;
    }
}

Nous pouvons maintenant écrire des implémentations pour les méthodes 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);
    }
}

Je suggère fortement de supprimer les méthodes getter et de déclarer les méthodes add surchargées pour chaque type de véhicule. Cela réduira les frais généraux liés aux visites lorsque vous n'en aurez pas besoin, par exemple, 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

Vous pouvez ajouter une méthode à la classe de véhicule pour imprimer le texte. Remplacez ensuite la méthode dans chaque classe de voiture spécialisée. Ensuite, ajoutez simplement toutes les voitures à la liste des véhicules. Et boucle la liste pour imprimer le texte.

2
Himanshu Ahire

Fait une restructuration de votre code. J'espère que cela fonctionne pour vous. Vérifie ça:

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

Avec le code ci-dessus, la classe devra connaître la collection à laquelle elle doit être ajoutée. Cela peut être considéré comme un inconvénient pour une bonne conception et il peut être surmonté en utilisant le modèle de conception du visiteur, comme indiqué dans la réponse acceptée ( Comment éviter 'une instance' lors de la mise en œuvre du modèle de conception en usine? ).

2
Anshuman

Je ne suis pas trop heureux avec les listes de voitures, bateaux et avions en premier lieu. Vous avez plusieurs exemples de réalité mais la liste n'est pas exhaustive - que se passe-t-il lorsque votre usine commence à fabriquer des sous-marins ou des roquettes?

Au lieu de cela, que diriez-vous d'une énumération avec les types voiture, bateau et avion. Vous avez un tableau de listes de véhicules.

Le véhicule générique a une propriété abstraite CatalogAs, les différents véhicules le mettent en œuvre et renvoient la valeur appropriée.

2
Loren Pechtel

Je sais que cela fait longtemps que cette question n'a pas été posée. J'ai trouvé http://www.nurkiewicz.com/2013/09/instanceof-operator-and-visitor-pattern.html qui semble utile. Partage ici au cas où quelqu'un serait intéressé. 

1
Hari Rao

Que se passe-t-il si les classes de véhicules sont hors de votre contrôle? Par exemple. vous l'avez d'une librairie tierce? Vous n’avez donc aucun moyen d’ajouter la méthode accept () du modèle Visitor. De plus, vous pourriez probablement ne pas aimer le code standard dans chacune des sous-classes de AVehicle et préférer tout mettre dans une classe spéciale en gardant vos classes propres ..__ Dans certains cas, il pourrait être préférable d’utiliser HashMap.

Dans votre exemple, utilisez simplement:

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

Le problème avec cette approche HashMap est que vous devez enregistrer toutes les classes possibles, y compris toutes les sous-classes connues. Bien que, si vous avez une hiérarchie énorme et qu'il ne soit pas nécessaire d'utiliser toutes les classes pour votre tâche, vous pouvez économiser beaucoup de travail en enregistrant dans la carte les tâches dont vous avez besoin. 

0
engilyin

Si j'avais un problème similaire, j'ai donc utilisé ce modèle. Pour mieux le comprendre, j'ai créé un simple dessin UML montrant la séquence d'éléments dans les commentaires (suivez les chiffres). J'ai utilisé la solution Vince Emighs ci-dessus. La solution de motif est plus élégante mais peut prendre un peu de temps pour vraiment comprendre. Cela nécessite une interface et une classe de plus que l'originale mais elles sont très simples.

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

0
Orhan