web-dev-qa-db-fra.com

Modèle d'usine Java avec des génériques

J'aimerais que ma BallUserInterfaceFactory renvoie une instance d'une interface utilisateur ayant le type générique approprié. Je suis coincé dans l'exemple ci-dessous en obtenant l'erreur:

Correspondance liée: la méthode générique getBaseballUserInterface (BASEBALL) de type BallUserInterfaceFactory n'est pas applicable pour les arguments (BALL). Le type inféré BALL n'est pas un substitut valide du paramètre lié

public class BallUserInterfaceFactory {
    public static <BALL extends Ball> BallUserInterface<BALL> getUserInterface(BALL ball) {

        if(ball instanceof Baseball){
            return getBaseballUserInterface(ball);
        }
        //Other ball types go here

        //Unable to create a UI for ball
        return null;
    }

    private static <BASEBALL extends Baseball> BaseballUserInterface<BASEBALL> getBaseballUserInterface(BASEBALL ball){
        return new BaseballUserInterface<BASEBALL>(ball);
    }
}

Je comprends qu’elle ne peut pas garantir que BALL soit une balle de baseball. Il existe donc une incompatibilité de type de paramètre dans l’appel de la méthode getBaseballUserInterface.

Si je transtypes le paramètre ball dans l'appel à la méthode getBaseballUserInterface, le message d'erreur suivant s'affiche:

Incompatibilité de type: impossible de convertir de BaseballUserInterface<Baseball> à BallUserInterface<BALL>

Parce qu'il ne peut pas garantir que ce que je retourne est le même type de balle.

Ma question est, quelle est la stratégie pour faire face à cette situation?

(Pour être complet, voici les autres classes requises dans l'exemple)

public class Ball {

}

public class Baseball extends Ball {

}

public class BallUserInterface <BALL extends Ball> {

    private BALL ball;

    public BallUserInterface(BALL ball){
        this.ball = ball;
    }
}

public class BaseballUserInterface<BASEBALL extends Baseball> extends BallUserInterface<BASEBALL>{

    public BaseballUserInterface(BASEBALL ball) {
        super(ball);
    }

}
22
FuryComputers

C'est une très bonne question.

Vous pouvez lancer bruyamment

    return (BallUserInterface<BALL>)getBaseballUserInterface((Baseball)ball);

La réponse est théoriquement imparfaite, puisque nous forçons BASEBALL=Baseball.

Cela fonctionne en raison de l'effacement. En fait, il dépend de l’effacement.

J'espère qu'il y a une meilleure réponse qui est reification safe .

3
irreputable

C'est un mauvais modèle. Plutôt que d'utiliser une méthode générique et une échelle si, vous devriez plutôt utiliser la surcharge. La surcharge élimine le besoin de ladder if et le compilateur peut s’assurer que la bonne méthode est appelée plutôt que d’attendre le moment de l’exécution.

par exemple.

public class BallUserInterfaceFactory {

    public static BallUserInterface<Baseball> getUserInterface(
            Baseball ball) {
        return new BallUserInterface<Baseball>(ball);
    }

    public static BallUserInterface<Football> getUserInterface(
            Football ball) {
        return new BallUserInterface<Football>(ball);
    }
}

De cette façon, vous obtenez également l'avantage supplémentaire des erreurs de compilation si votre code ne peut pas créer un BallUserInterfacepour la balle appropriée.


Pour éviter l’échelle if, vous pouvez utiliser une technique appelée double dispatch. En substance, nous utilisons le fait que l'instance sait à quelle classe elle appartient et appelle la méthode de fabrique appropriée pour nous. Pour que cela fonctionne, Balldoit avoir une méthode qui retourne le BallInterfaceapproprié.

Vous pouvez rendre la méthode abstraite ou fournir une implémentation par défaut qui lève une exception ou retourne null. Ball et Baseball devraient maintenant ressembler à ceci:

public abstract class Ball<T extends Ball<T>> {
    abstract BallUserInterface<T> getBallUserInterface();
}

.

public class Baseball extends Ball<Baseball> {
    @Override
    BallUserInterface<Baseball> getBallUserInterface() {
        return BallUserInterfaceFactory.getUserInterface(this);
    }
}

Pour rendre les choses un peu plus propres, il est préférable de rendre le paquet getBallUserInterfaceprivé et de fournir un getter générique dans BallUserInterfaceFactoryname__. L'usine peut alors gérer des vérifications supplémentaires, comme pour les exceptions nulles et éventuelles. par exemple.

public class BallUserInterfaceFactory { 
    public static BallUserInterface<Baseball> getUserInterface(
            Baseball ball) {
        return new BallUserInterface<Baseball>(ball);
    }   
    public static <T extends Ball<T>> BallUserInterface<T> getUserInterface(
            T ball) {
        return ball.getBallUserInterface();
    }
}

Le modèle de visiteur

Comme indiqué dans les commentaires, l'un des problèmes de ce qui précède est que les classes Balldoivent connaître l'interface utilisateur, ce qui est hautement indésirable. Vous pouvez toutefois utiliser le modèle de visiteur, ce qui vous permet d’utiliser la double répartition, mais également de découpler les différentes classes Ballet l’interface utilisateur.

Tout d’abord, les classes de visiteurs nécessaires et les fonctions d’usine:

public interface Visitor<T> {
    public T visit(Baseball ball);
    public T visit(Football ball);
}

public class BallUserInterfaceVisitor implements Visitor<BallUserInterface<? extends Ball>> {
    @Override
    public BallUserInterface<Baseball> visit(Baseball ball) {
        // Since we now know the ball type, we can call the appropriate factory function
        return BallUserInterfaceFactory.getUserInterface(ball);
    }   
    @Override
    public BallUserInterface<Football> visit(Football ball) {
        return BallUserInterfaceFactory.getUserInterface(ball);
    }
}

public class BallUserInterfaceFactory {
    public static BallUserInterface<? extends Ball> getUserInterface(Ball ball) {
        return ball.accept(new BallUserInterfaceVisitor());
    }
    // other factory functions for when concrete ball type is known
}

Vous noterez que le visiteur et la fonction usine doivent utiliser des caractères génériques. Ceci est nécessaire pour la sécurité du type. Puisque vous ne savez pas quel type de balle a été passé, la méthode ne peut pas être sûre de l'interface utilisateur renvoyée (à part qu'il s'agit d'une interface utilisateur de balle).

Deuxièmement, vous devez définir une méthode abstraite acceptsur Ballqui accepte un Visitorname__. Chaque implémentation concrète de Balldoit également implémenter cette méthode pour que le modèle de visiteur fonctionne correctement. L'implémentation a exactement la même apparence, mais le système de types garantit l'envoi des méthodes appropriées.

public interface Ball {
    public <T> T accept(Visitor<T> visitor);
}

public class Baseball implements Ball {
    @Override
    public <T> T accept(Visitor<T> visitor) {
        return visitor.visit(this);
    }
}

Enfin, un peu de code pouvant rassembler tout cela:

Ball baseball = new Baseball();
Ball football = new Football();

List<BallUserInterface<? extends Ball>> uiList = new ArrayList<>();

uiList.add(BallUserInterfaceFactory.getUserInterface(baseball));
uiList.add(BallUserInterfaceFactory.getUserInterface(football));

for (BallUserInterface<? extends Ball> ui : uiList) {
    System.out.println(ui);
}

// Outputs:
// ui.BaseballUserInterface@37e247e2
// ui.FootballUserInterface@1f2f0ce9
21
Dunes
public class BaseballUserInterface extends BallUserInterface<Baseball> {

    public BaseballUserInterface(Baseball ball) {
        super(ball);
    }
}

Vous utilisez BallUserInterface à la suite de la méthode d'usine. Donc, on peut cacher quelle boule de béton est utilisée:

public class BallUserInterfaceFactory {

public static BallUserInterface<?> getUserInterface(Ball ball) {

        if(ball instanceof Baseball){
            return getBaseballUserInterface((Baseball)ball);
        }

        return null;
    }

    private static BaseballUserInterface getBaseballUserInterface(Baseball ball){
        return new BaseballUserInterface(ball);
    }
}

Si le client est intéressé par le type de la balle, vous devez proposer une méthode d'usine avec la balle en béton comme paramètre:

public static BaseballUserInterface getUserInterface(Baseball ball){
    return new BaseballUserInterface(ball);
}
0
ollins