¿Cómo instanciar una implementación específica, basada en una determinada clase?

Tengo varios objetos extendiendo un bean:

public class Fruit {}

public class Banana extends Fruit {}

public class Pear extends Fruit {}

Y tengo una interfaz con una implementación diferente, una para cada bean.

public interface Milkshake {
    public String doMilkshake();
}

public class FruitMilkshake implements Milkshake {
    public String doMilkshake() {
        return "Fruit Milkshake!";
    }
}

public class BananaMilkshake implements Milkshake {
    public String doMilkshake() {
        return "Banana Milkshake!";
    }
}

public class PearMilkshake implements Milkshake {
    public String doMilkshake() {
        return "Pear Milkshake!";
    }
}

¿Cómo puedo instanciar la implementación correcta en función del tipo concreto de mi bean?

Por ahora he usado tipificación y un Mapa para "mapear" la implementación correcta. Como esto:

public void hungry(Fruit fruit) {
    Map<String, String> obj2impl = new HashMap<String, String>();
    obj2impl.put("Fruit", "FruitMilkshake");
    obj2impl.put("Banana", "BananaMilkshake");
    obj2impl.put("Pear", "PearMilkshake");

    String name = fruit.getClass().getCanonicalName();
    String implName = obj2impl.get(name);
    Milkshake milkshake = (Milkshake) Class.forName(implName).newInstance();

    milkshake.doMilkshake(fruit);
}



public interface Milkshake <T t> {
    public String doMilkshake(T t);
}

public class FruitMilkshake implements Milkshake<Fruit> {
    public String doMilkshake(Fruit fruit) {
        return "Fruit Milkshake!";
    }
}

public class BananaMilkshake implements Milkshake<Banana> {
    public String doMilkshake(Banana banana) {
        return "Banana Milkshake!";
    }
}

public class PearMilkshake implements Milkshake<Pear> {
    public String doMilkshake(Pear pear) {
        return "Pear Milkshake!";
    }
}

¿Mejores formas de lograr esto?

preguntado el 24 de agosto de 12 a las 08:08

No debes recrear el mapa de frutas en cada invocación de hungry. Pon ese mapa como una variable de instancia en su lugar. O enciende el nombre de la fruta. -

2 Respuestas

¿Mejores formas de lograr esto?

Bueno, un punto de partida sería evitar reflexionar sobre el nombre de la clase:

Map<String, Class<? extends Milkshake>> obj2impl =
    new HashMap<String, Class<? extends Milkshake>>();
obj2impl.put("Fruit", FruitMilkshake.class);
obj2impl.put("Banana", BananaMilkshake.class);

...

Milkshake milkshake = obj2impl.get(text).newInstance();

Eso todavía requiere que tengas un constructor sin parámetros en cada implementación, y todavía siempre crea una nueva instancia. Si usas un Provider-como concepto, puedes evitar esto:

Map<String, Provider<Milkshake>> map = ...;
// Fill the map with providers, some of which could create a new instance,
// and some could reuse an existing one

...

Milkshake milkshake = map.get(text).get();

EDITAR: después de volver a leer la publicación, también puede deshacerse de la parte del texto y tener un Map<Class<?>, Provider<Milkshake>>. Evite codificar los nombres de las clases si es posible.

Por supuesto, si tu Fruit la clase tuvo un makeMilkshake método abstracto, eso sería aún mejor...

Respondido 24 ago 12, 08:08

Obviamente no estoy usando esos nombres. Fue solo un ejemplo. - Enriquecer

@Enrichman: no estaba sugiriendo que esos fueran los nombres reales, pero estás usando el texto del nombre de la clase en el mapa, cuando puede usar un literal de clase en su lugar. Ese es mi punto. - jon skeet

Ah está bien, lo tengo. Estás bien. :) Por cierto, estoy usando Guice en mi proyecto, por lo que las cosas del proveedor serán útiles. IoC es la única forma en que pienso. - Enriquecer

public class Fruit {
    public Class<? extends Milkshake> getMilkshakeClass() {
        return Milkshake.class;
    }
}

public class Banana extends Fruit {
    public Class<? extends Milkshake> getMilkshakeClass() {
        return Banana.class;
    }
}

public class Pear extends Fruit {
    public Class<? extends Milkshake> getMilkshakeClass() {
        return PearMilkshake.class;
    }
}

Incluso es posible que desee crear una clase AbstractFruit que tenga los métodos utilizados anteriormente como un método abstracto que deben anular

public abstract class AbstractFruit {
    public abstract Class<? extends Milkshake> getMilkshakeClass();
}

Y en lugar de hacer que extiendan Fruit, extienda eso.

Respondido 24 ago 12, 08:08

Ahora estás atando el Fruit, Banana and Pear tener conocimiento sobre Milkshake. ¿Por qué tendrían algún conocimiento sobre batidos? - estudiante de primer año

¿Porque puedes tomar una fruta y hacer un batido al respecto? Es una idea completamente plausible. No ha dicho qué conocimiento exacto tienen el uno del otro, pero todos son públicos, así que asumo que todos se conocen. alex coleman

Si planea tener toneladas de opciones diferentes como esa, ¿cómo sabría qué cosa hacer? Para cada fruta dada, debe haber solo una opción para que el artículo produzca. De lo contrario, el método hambriento no sabría qué usar. Entonces, si cada fruta tiene un elemento correspondiente para hacer, esto funciona bien: alex coleman

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.