patterns - Fábrica polimórfica/getInstance() en Java
pool design pattern (7)
Mi objetivo es crear un conjunto de objetos, cada uno de los cuales tiene un identificador único. Si un objeto ya existe con ese identificador, quiero usar el objeto existente. De lo contrario, quiero crear uno nuevo. Intento no usar la palabra Singleton porque sé que es una mala palabra aquí ...
Puedo usar un método de fábrica:
// A map of existing nodes, for getInstance.
private static Map<String, MyClass> directory = new HashMap<String, MyClass>();
public static MyClass getInstance(String name) {
MyClass node = directory.get(name);
if(node == null) {
node == new MyClass(name);
}
return node;
}
O igualmente, podría tener un método MyClassFactory por separado.
Pero tenía la intención de subclase MyClass:
public class MySubClass extends MyClass;
Si no hago más, e invoco MySubClass.getInstance ():
MyClass subclassObj = MySubClass.getInstance("new name");
... entonces subclassObj será una MyClass simple, no una MySubClass.
Sin embargo, anular getInstance () en cada subclase parece hacky.
¿Hay una buena solución que me falta?
Esa es la versión generalizada de la pregunta. Más detalles, ya que los contestadores los pidieron.
El programa es para generar un gráfico dirigido de dependencias entre nodos que representan piezas de software. Las subclases incluyen programas Java, servicios web, procedimientos de SQL almacenado, activadores basados en mensajes, etc.
Por lo tanto, cada clase "es-un" elemento en esta red y tiene métodos para navegar y modificar las relaciones de dependencia con otros nodos. La diferencia entre las subclases será la implementación del método populate()
usado para configurar el objeto desde la fuente apropiada.
Digamos que el nodo llamado ''login.java'' aprende que tiene una dependencia en ''checkpasswd.sqlpl'':
this.addDependency( NodeFactory.getInstance("checkpasswd.sqlpl"));
El problema es que el objeto checkpasswd.sqlpl puede o no existir en este momento.
¿Has mirado a Guice? No estoy seguro si esto resolverá su problema exactamente, pero actúa como una fábrica genérica y un contenedor de inyección de dependencia, y elimina las claves de cadena segura que no son de tipo.
El método estático se define en la clase principal, y también se llama estáticamente. Entonces, no hay manera de saber en el método que lo ha llamado en la subclase. El compilador java probablemente resuelva la llamada de forma estática a una llamada a la clase principal.
Por lo tanto, necesitará volver a implementar el método estático en sus clases secundarias como propone, o hacer que no sean estáticos para que pueda heredar (en una jerarquía de objetos de fábrica, no clases), o pasar un parámetro para indicar el tipo que desea crear.
Mira el método EnumSet.noneOf (). Tiene un problema similar al tuyo y lo resuelve pasando el método java.lang.Class. Puedes usar newInstance en la clase. Pero personalmente, usaría objetos de fábrica en lugar de clases con métodos estáticos.
La clase Class está parametrizada con el tipo de instancia que puede crear. (por ejemplo: Class <String> es una fábrica para instancias String).
No veo ninguna forma de desplazarme sabiendo el tipo de instancia que debería crearse cuando obtieneOrCreate usando el método de fábrica aquí, así que recomendaría pasarlo al método y parametrizar en el tipo que se está generando:
private static Map<String, MyClass> directory = new HashMap<String, MyClass>();
public static <T extends MyClass> T getInstance(String name, Class<T> generatorClass)
{
MyClass node = directory.get(name);
if(node == null) {
node = generatorClass.getConstructor(String.class).newInstance(name);
directory.put(name, node);
}
return node;
}
Además, noté que no estabas colocando los nodos recién construidos en el directorio, supongo que eso es un descuido. También podría sobrecargar este método con otro que no tomara un generador y un código fijo al tipo predeterminado:
public static MyClass getInstance(String name) {
return getInstance(name, MyClass.class);
}
Pareces dar a entender que en alguna parte sabes qué clase debería ser si no existe. Si implementa esa lógica en su fábrica, debería obtener las clases correctas.
De esta forma, tampoco debería necesitar saber qué clase realmente fue devuelta de fábrica.
También es probable que haga de ''MyClass'' una interfaz si está considerando un patrón de fábrica.
Probablemente quiera inyección de dependencia. Generalizaría lo que estás tratando de hacer de alguna manera.
Además, la herencia probablemente tampoco sea exactamente lo que necesitas. Nunca lo use a menos que pueda pasar la prueba "es-a". Si su clase heredada "tiene una" etiqueta de cadena única (esencialmente eso es lo que parece ser su clase) no significa que "es-a" ...
Sin embargo, uno podría tener el otro. Probablemente haya bastantes soluciones, pero A) no publicó su lista completa de requisitos, así que podemos adivinar, y B) lo que probablemente quiera es la inyección de dependencia. :)
Después de leer tu explicación del problema, creo que te resulta muy difícil subclasificar en la construcción de tu gráfico. Creo que el problema se vuelve mucho más simple si se separa el gráfico de dependencia de la "información del programa"
usa una interfaz como:
Public Interface Node<T> {
public Object<T> getObject();
public String getKey();
public List<String> getDependencies();
public void addDependence(String dep);
}
y luego usa una fábrica para instanciar tus nodos
El patrón parece ser una especie de Flyweight (estructuralmente, si no es una combinación perfecta para la intención).
El método populate
, como se describe, podría asignarse al patrón de Template
, aunque no necesariamente aborda las preocupaciones expresadas.
Lo que sugeriría es una generalización de la fábrica, con un método create
(en lugar de getInstance
, que sí implica un Singleton, de todos modos) para los diversos tipos que esperas.
public static MyClass createJavaNode(String name, <differentiator>);
public static MyClass createSqlPlNode (String name, <differentiator>);
.
.
.
El conocimiento sobre cómo un name
asigna a un <differentiator>
es realmente una opción de implementación. Idealmente, habría una create
polimórfica y la diferenciación sería por tipo de node
. El método create
devuelve MyClass
, pero realmente devuelve las subclases. Consideraría seriamente hacer de MyClass
una interfaz o una clase abstracta, con un método de populate
abstract
(ahí está la Template
).
A partir de la descripción, realmente parece que el comportamiento de creación es diferente entre los tipos, no el comportamiento de las subclases en sí, por lo que el caso de la refactorización de MyClass
para una interfaz o clase abstracta se fortalece.
Parafraseando a RA Heinlein, TANSTAAFL
- No hay tal cosa como un almuerzo gratis - en algún lugar debe existir el conocimiento de cómo crear los diversos tipos en la aplicación. Así que su elección es poner ese conocimiento en la clase Factory, como han expresado algunas de las otras respuestas, o separar la decisión de qué implementación se crea a partir de cómo se creó. Parece que hay una capacidad de escanear un sistema de archivos y recopilar los Node
desde él. Ese código conocerá (o puede) el tipo de Node
(la respuesta al qué ) que debería crearse.
Ya sea que se implemente como un switch
o en una forma más OO (una de las veces en que el envío controlado por tabla sería bueno) depende de usted. Si esto es algo que podría ser útil, puedo ampliarlo aquí.
Por cierto, si el comportamiento del núcleo de MyClass
necesita extenderse o modificarse para algunas subclases, es allí donde un Decorator
podría ser útil.
Respuesta Original:
Puede considerar el Decorador o los Patrones de Diseño de Plantilla como alternativas.