java - method - static interface c#
Alternativas a métodos estáticos en Java (9)
¿Por qué no usar anotaciones? Se ajustan bastante bien a lo que estás haciendo: agregar metainformación (aquí una consulta SQL) a una clase.
Estoy haciendo un mini ORM para un programa Java que estoy escribiendo ... hay una clase para cada tabla en mi base de datos, todas heredan de ModelBase
.
ModelBase
es abstracto y proporciona un conjunto de métodos estáticos para buscar y vincular objetos desde el DB, por ejemplo:
public static ArrayList findAll(Class cast_to_class) {
//build the sql query & execute it
}
Entonces puede hacer cosas como ModelBase.findAll(Albums.class)
para obtener una lista de todos los álbumes persistentes. 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 métodos estáticos en Java. Pero no quiero hacer que getSelectSQL()
un método de instancia en el Album
porque entonces necesito crear una instancia para obtener una cadena que sea realmente estática en el comportamiento.
En este momento, findAll()
usa reflection 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 bastante asqueroso.
Entonces, ¿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 de nuevo!
¿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)?
Aunque, estoy totalmente de acuerdo en el punto de que "estático está mal usar aquí", entiendo lo que intentas abordar aquí. Todavía el comportamiento de la instancia debe ser la forma de trabajar, pero si insiste, esto es lo que haría:
A partir de tu comentario "Necesito crear una instancia para obtener una cadena que sea realmente estática en el comportamiento"
No es completamente 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 solo necesitas cambiar los "datos" que usa la clase 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 declaraciones 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 contentos a los clientes de su clase (y a sí mismo), ya que no necesita "crear una instancia" para hacer el trabajo.
// Client usage:
...
List albums = ModelBase.findAll( Album.class );
...
Otro enfoque es crear las instancias desde atrás y mantener la interfaz de cliente intacta al usar métodos de instancia, los métodos están marcados como "protegidos" para evitar tener una invocación externa. De manera similar a la muestra anterior, también puedes 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 necesita cambiar el código del cliente, y aún así tener el poder del 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 vs. 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.
Como se sugirió, puede usar anotaciones, o puede mover los métodos estáticos a 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 buen olor tener objetos sin ningún estado.
Estoy de acuerdo con Gizmo: estás mirando 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.
La estática 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 mesa 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 tienes 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 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.
Puede tener sus métodos SQL como métodos de instancia en una clase separada.
A continuación, pase el objeto modelo al constructor de esta nueva clase y llame a sus métodos para obtener SQL.
Si está pasando una clase para encontrar todo, ¿por qué no puede pasar una clase para obtenerSelectSQL en ModelBase?
Vaya, este es un mejor ejemplo de algo que pedí 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 la necesidad de instanciar la clase en cuestión y se siente ''Derecha''.
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 realmente "limpio".
Ver mi pregunta anterior (relacionada) aquí: Cómo manejar los campos estáticos que varían según la clase de implementación . Pensé que las respuestas eran realmente cojas y no captaron el punto. Tu pregunta está mucho mejor redactada.
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 universal "select * from" + 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 echaré un vistazo a las anotaciones. Aunque no puedo evitar preguntarme qué hicieron las personas con estos problemas antes de que hubiera reflexión.