2008-09-30 24 views
13

Estoy haciendo un mini ORM para un programa Java que estoy escribiendo ... hay una clase para cada tabla en mi db, todas heredan de ModelBase.Alternativas a métodos estáticos en Java

ModelBase es abstracto & proporciona un montón de métodos estáticos para encontrar objetos & vinculantes desde el PP, por ejemplo:

public static ArrayList findAll(Class cast_to_class) { 
    //build the sql query & execute it 
} 

para que pueda hacer cosas como ModelBase.findAll(Albums.class) para obtener una lista de todos los álbumes persistido. Mi problema es que en este contexto estático, necesito obtener la cadena sql apropiada del Álbum de la clase concreta. No puedo tener un método estático como

public class Album extends ModelBase { 
    public static String getSelectSQL() { return "select * from albums.....";} 
} 

porque no hay polimorfismo para los métodos estáticos en Java. Pero no quiero hacer getSelectSQL() un método de instancia en Album porque entonces necesito crear una instancia de la misma para obtener una cadena que es realmente estática en el comportamiento.

Por el momento, findAll() utiliza la reflexión para obtener el SQL apropiado para la clase en cuestión:

select_sql = (String)cast_to_class.getDeclaredMethod("getSelectSql", new Class[]{}).invoke(null, null); 

Pero eso es muy grave.

¿Alguna idea? Es un problema general que tengo una y otra vez: la incapacidad de especificar métodos estáticos abstractos en clases o interfaces. Sé por qué el polimorfismo del método estático no funciona y no funciona, ¡pero eso no me impide desear usarlo otra vez!

¿Hay algún patrón/construcción que me permita asegurar que las subclases concretas X e Y implementen un método de clase (o en su defecto, una constante de clase)?

+0

Estoy un poco confundido por qué estás escribiendo tu propio ORM cuando hay excelentes opciones como iBatis e Hibernate. Por cierto, me parece que estás tratando de hacer que un método estático de Java funcione como un método de clase Ruby. Si es así, no funcionará :( – Alan

Respuesta

2

Sin embargo, estoy totalmente de acuerdo en el punto de que "estático está mal usar aquí", entiendo algo de lo que intentas abordar aquí. Todavía el comportamiento de la instancia debería ser la forma de trabajar, pero si insistes, esto es lo que haría:

Comenzando con tu comentario "Necesito crear una instancia para obtener una cadena que sea realmente estática en el comportamiento"

No es del todo correcto. Si te ves bien, no estás cambiando el comportamiento de tu clase base, simplemente cambiando el parámetro para un método. En otras palabras, estás cambiando los datos, no el algoritmo.

La herencia es más útil cuando una nueva subclase quiere cambiar la forma en que funciona un método, si tan solo necesitas cambiar los "datos" que la clase utiliza para trabajar, probablemente un enfoque como este haría el truco.

class ModelBase { 
    // Initialize the queries 
    private static Map<String,String> selectMap = new HashMap<String,String>(); static { 
     selectMap.put("Album", "select field_1, field_2 from album"); 
     selectMap.put("Artist", "select field_1, field_2 from artist"); 
     selectMap.put("Track", "select field_1, field_2 from track"); 
    } 

    // Finds all the objects for the specified class... 
    // Note: it is better to use "List" rather than "ArrayList" I'll explain this later. 
    public static List findAll(Class classToFind) { 
     String sql = getSelectSQL(classToFind); 
     results = execute(sql); 
     //etc... 
     return .... 
    } 

    // Return the correct select sql.. 
    private static String getSelectSQL(Class classToFind){ 
     String statement = tableMap.get(classToFind.getSimpleName()); 
     if(statement == null) { 
      throw new IllegalArgumentException("Class " + 
       classToFind.getSimpleName + " is not mapped"); 
     } 
     return statement; 

    } 
} 

Es decir, mapea todas las afirmaciones con un Mapa. El siguiente paso "obvio" es cargar el mapa desde un recurso externo, como un archivo de propiedades, o un xml o incluso (por qué no) una tabla de base de datos, para una mayor flexibilidad.

De esta forma puede mantener felices a sus clientes de la clase (y a usted), porque no necesita "crear una instancia" para hacer el trabajo.

// Client usage: 

... 
List albums = ModelBase.findAll(Album.class); 

...

Otro enfoque es el de crear las instancias de atrás, y mantener su interfaz de cliente intacta durante el uso de los métodos de instancia, los métodos se marcan como "protegido" para evitar tener invocación externa. De un modo similar de la muestra anterior también se puede hacer esto

// Second option, instance used under the hood. 
class ModelBase { 
    // Initialize the queries 
    private static Map<String,ModelBase> daoMap = new HashMap<String,ModelBase>(); static { 
     selectMap.put("Album", new AlbumModel()); 
     selectMap.put("Artist", new ArtistModel()); 
     selectMap.put("Track", new TrackModel()); 
    } 

    // Finds all the objects for the specified class... 
    // Note: it is better to use "List" rather than "ArrayList" I'll explain this later. 
    public static List findAll(Class classToFind) { 
     String sql = getSelectSQL(classToFind); 
     results = execute(sql); 
     //etc... 
     return .... 
    } 

    // Return the correct select sql.. 
    private static String getSelectSQL(Class classToFind){ 
     ModelBase dao = tableMap.get(classToFind.getSimpleName()); 
     if(statement == null) { 
      throw new IllegalArgumentException("Class " + 
       classToFind.getSimpleName + " is not mapped"); 
     } 
     return dao.selectSql(); 
    } 
    // Instance class to be overrided... 
    // this is "protected" ... 
    protected abstract String selectSql(); 
} 
class AlbumModel extends ModelBase { 
    public String selectSql(){ 
     return "select ... from album"; 
    } 
} 
class ArtistModel extends ModelBase { 
    public String selectSql(){ 
     return "select ... from artist"; 
    } 
} 
class TrackModel extends ModelBase { 
    public String selectSql(){ 
     return "select ... from track"; 
    } 
} 

Y no es necesario cambiar el código de cliente, y todavía tienen el poder de polimorfismo.

// Client usage: 

... 
List albums = ModelBase.findAll(Album.class); // Does not know , behind the scenes you use instances. 

...

espero que esto ayude.

Una nota final sobre el uso de List frente a ArrayList. Siempre es mejor programar en la interfaz que en la implementación, de esta manera usted hace que su código sea más flexible. Puede usar otra implementación de lista que sea más rápida o haga otra cosa, sin cambiar su código de cliente.

0

Si está pasando una clase para encontrar todo, ¿por qué no puede pasar una clase para obtenerSelectSQL en ModelBase?

1

¿Por qué no utilizar las anotaciones? Se ajustan bastante bien a lo que estás haciendo: agregar metainformación (aquí una consulta SQL) a una clase.

0

asterite: ¿quiere decir que getSelectSQL existe solo en ModelBase, y utiliza el aprobado en clase para hacer un nombre de tabla o algo así? No puedo hacer eso, porque algunos de los Modelos tienen construcciones de selección diferentes, así que no puedo usar un "select * universal" de "+ classToTableName() ;. Y cualquier intento de obtener información de los Modelos sobre su construcción seleccionada se encuentra con el mismo problema de la pregunta original: usted necesita una instancia del Modelo o alguna reflexión sofisticada.

gizmo: Definitivamente voy a echar un vistazo a las anotaciones. Aunque no puedo evitar preguntarme qué hicieron las personas con estos problemas antes de que hubiera reflexión.

+0

Luego debería echar un vistazo a ORM en lenguajes que no admiten la reflexión, como C++. Porque, hasta donde yo sé, casi todos los ORM de Java (si no todos) usan reflexiones en algún momento. – gizmo

+0

Por favor, haga esos comentarios como ... comentarios;) Una respuesta solo por respuesta. De esa forma, gizmo y asterite * editarán * sus respuestas para completarlas, o dejarán comentarios adicionales para continuar la discusión iniciada por sus preguntas – VonC

0

Puede tener sus métodos de SQL como métodos de instancia en una clase separada.
Luego pase el objeto modelo al constructor de esta nueva clase y llame a sus métodos para obtener SQL.

-1

Estoy de acuerdo con Gizmo: está buscando anotaciones o algún tipo de archivo de configuración. Echaré un vistazo a Hibernate y otros marcos ORM (¡y tal vez incluso bibliotecas como log4j!) Para ver cómo manejan la carga de la metainformación de nivel de clase.

No todo puede o debe hacerse programáticamente, creo que este puede ser uno de esos casos.

0

Wow - este es un ejemplo mucho mejor de algo que le pregunté anteriormente en términos más generales - cómo implementar propiedades o métodos que son estáticos para cada clase de implementación de una manera que evite la duplicación, proporciona acceso estático sin necesidad de instanciar el clase preocupada y siente 'Correcto'.

Respuesta corta (Java o .NET): No se puede. Respuesta más larga: puede hacerlo si no le importa usar una anotación de nivel de clase (reflejo) o instanciar un objeto (método de instancia), pero ninguno de ellos es verdaderamente "limpio".

Ver mi pregunta anterior (relacionada) aquí: How to handle static fields that vary by implementing class Pensé que las respuestas eran realmente flojas y no entendí el punto. Tu pregunta está mucho mejor redactada.

1

Como se sugirió, podría utilizar anotaciones, o se puede mover los métodos estáticos a los objetos de fábrica:

public abstract class BaseFactory<E> { 
    public abstract String getSelectSQL(); 
    public List<E> findAll(Class<E> clazz) { 
     // Use getSelectSQL(); 
    } 
} 

public class AlbumFactory extends BaseFactory<Album> { 
    public String getSelectSQL() { return "select * from albums....."; } 
} 

pero no es un olor muy bueno tener objetos sin estado.

4

Static es lo incorrecto de usar aquí.

Conceptualmente estático es incorrecto porque es solo para servicios que no corresponden a un objeto real, físico o conceptual. Tiene una cantidad de tablas, y cada una debe estar representada por un objeto real en el sistema, no solo ser una clase. Parece que es un poco teórico, pero tiene consecuencias reales, como veremos.

Cada tabla es de una clase diferente, y eso está bien. Como solo puede tener una de cada tabla, limite el número de instancias de cada clase a una (use una bandera, no la convierta en Singleton). Haga que el programa cree una instancia de la clase antes de acceder a la tabla.

Ahora tiene un par de ventajas. Puede usar toda la potencia de la herencia y la anulación ya que sus métodos ya no son estáticos. Puede usar el constructor para realizar cualquier inicialización, incluida la asociación de SQL con la tabla (SQL que sus métodos pueden usar más adelante). Esto debería hacer desaparecer todos tus problemas anteriores, o al menos ser mucho más simples.

Parece que hay trabajo extra al tener que crear el objeto y la memoria extra, pero es realmente trivial en comparación con las ventajas. Algunos bytes de memoria para el objeto no se notarán, y un puñado de llamadas de constructor tomará tal vez diez minutos en agregarse. Contra eso está la ventaja de que el código para inicializar cualquier tabla no necesita ejecutarse si la tabla no se usa (no se debe llamar al constructor). Descubrirás que simplifica mucho las cosas.

Cuestiones relacionadas