Pregunta

Estoy haciendo un mini ORM para un programa Java que estoy escribiendo ... hay una clase para cada tabla en mi base de datos, todo heredado de ModelBase .

ModelBase es abstracto & amp; proporciona un montón de métodos estáticos para encontrar & amp; enlazar objetos de la base de datos, 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 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 los métodos estáticos en Java. Pero no quiero hacer que getSelectSQL () sea un método de instancia en Album porque entonces necesito crear una instancia de él solo para obtener una cadena que sea realmente estática en comportamiento.

En este momento, findAll () usa 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 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 método estático de polimorfismo no funciona y no puede funcionar, ¡pero eso no me impide querer usarlo una vez más!

¿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)?

¿Fue útil?

Solución

Aunque, estoy totalmente de acuerdo en el punto de que "Estático es lo incorrecto para usar aquí", entiendo lo que estás tratando de abordar aquí. Aún así, el comportamiento de la instancia debería ser la forma de trabajar, pero si insiste, esto es lo que haría:

A partir de su comentario "Necesito crear una instancia de él solo para obtener una cadena que tenga un comportamiento realmente estático".

No es completamente correcto. Si se ve bien, no está cambiando el comportamiento de su clase base, solo está cambiando el parámetro para un método. En otras palabras, está 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 necesita cambiar los "datos". la clase suele funcionar, probablemente un enfoque como este sería suficiente.

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, mapear todas las declaraciones con un mapa. Lo "obvio" el siguiente paso para esto 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 mayor flexibilidad.

De esta manera puedes mantener contentos a tus clientes de clase (y a ti mismo), porque no necesitas '' 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 intacta la interfaz del cliente mientras se utilizan métodos de instancia, los métodos se marcan como "protegidos". para evitar tener una invocación externa. De manera similar a la muestra anterior, también 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 necesita cambiar el código del cliente, y todavía tiene 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 haces que tu 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.

Otros consejos

Estático es lo incorrecto para usar aquí.

Conceptualmente, la estática está mal porque es solo para servicios que no corresponden a un objeto real, físico o conceptual. Tiene varias tablas, y cada una debe estar representada por un objeto real en el sistema, no solo 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 tienes un par de ventajas. Puede usar todo el poder de herencia y 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 que todos sus problemas anteriores desaparezcan, o al menos ser mucho más simples.

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

¿Por qué no usar anotaciones? Encajan bastante bien con lo que está haciendo: agregar metainformación (aquí una consulta SQL) a una clase.

Como se sugiere, puede usar anotaciones o 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 muy buen olor tener objetos sin ningún estado.

Si está pasando una clase a findAll, ¿por qué no puede pasar una clase a getSelectSQL en ModelBase?

asterita: ¿quiere decir que getSelectSQL existe solo en ModelBase y utiliza el pasado en clase para crear un nombre de tabla o algo así? No puedo hacer eso, porque algunos de los Modelos tienen construcciones select muy diferentes, por lo 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: necesita una instancia del Modelo o alguna reflexión elegante.

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

Podría tener sus métodos 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.

Wow: este es un ejemplo mucho mejor de algo que 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 la clase preocupado y se siente "correcto".

Respuesta corta (Java o .NET): No puedes. Respuesta más larga: puede hacerlo si no le importa usar una anotación de nivel de clase (reflexión) o crear instancias de un objeto (método de instancia) pero ninguno de los dos está realmente 'limpio'.

Vea mi pregunta anterior (relacionada) aquí: Cómo manejar campos estáticos que varían según la clase de implementación Pensé que las respuestas eran realmente poco convincentes y perdí el punto. Su pregunta está mucho mejor redactada.

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

No todo puede o debe hacerse mediante programación, creo que este puede ser uno de esos casos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top