Domanda

Sto realizzando un mini ORM per un programma Java che sto scrivendo ... c'è una classe per ogni tabella nel mio db, tutta ereditata da ModelBase .

ModelBase è astratto & amp; fornisce un sacco di metodi statici per trovare & amp; rilegatura di oggetti dal db, ad esempio:

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

Quindi puoi fare cose come ModelBase.findAll (Albums.class) per ottenere un elenco di tutti gli album persistenti. Il mio problema è che in questo contesto statico, devo ottenere la stringa sql appropriata dalla classe concreta Album. Non posso avere un metodo statico come

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

perché non esiste polimorfismo per metodi statici in Java. Ma non voglio rendere getSelectSQL () un metodo di istanza in Album perché quindi ho bisogno di crearne un'istanza solo per ottenere una stringa che sia veramente statica in comportamento.

Al momento, findAll () usa la riflessione per ottenere il sql appropriato per la classe in questione:

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

Ma è piuttosto disgustoso.

Quindi qualche idea? È un problema generale che sto riscontrando più e più volte: l'impossibilità di specificare metodi statici astratti in classi o interfacce. Conosco perché il polimorfismo del metodo statico non funziona e non può funzionare, ma ciò non mi impedisce di voler usarlo di nuovo!

C'è qualche modello / costrutto che mi permetta di garantire che le sottoclassi concrete X e Y implementino un metodo di classe (o, in mancanza, una costante di classe!)?

È stato utile?

Soluzione

Anche se, sono totalmente d'accordo sul punto "statico è la cosa sbagliata da usare qui", capisco cosa stai cercando di affrontare qui. Comunque il comportamento dell'istanza dovrebbe essere il modo di lavorare, ma se insisti questo è quello che farei:

A partire dal tuo commento " Devo crearne un'istanza solo per ottenere una stringa che abbia un comportamento veramente statico "

Non è completamente corretto. Se hai un bell'aspetto, non stai cambiando il comportamento della tua classe base, ma solo cambiando il parametro per un metodo. In altre parole, stai modificando i dati, non l'algoritmo.

L'ereditarietà è più utile quando una nuova sottoclasse vuole cambiare il modo in cui un metodo funziona, se devi solo cambiare i "dati" la classe usa per funzionare probabilmente un approccio come questo farebbe il trucco.

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;

    }
}

Cioè, mappare tutte le dichiarazioni con una Mappa. Il "ovvio" il prossimo passo è caricare la mappa da una risorsa esterna, come un file delle proprietà, o un file xml o persino (perché no) una tabella di database, per una maggiore flessibilità.

In questo modo puoi rendere felici i tuoi clienti di classe (e te stesso), perché non hai bisogno di " creare un'istanza " per fare il lavoro.

// Client usage:

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

...

Un altro approccio è quello di creare le istanze da dietro e mantenere intatta l'interfaccia client mentre si utilizzano i metodi di istanza, i metodi sono contrassegnati come "protetti". per evitare invocazioni esterne. Allo stesso modo del campione precedente puoi anche farlo

// 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";
    }
}

E non è necessario modificare il codice client e avere ancora il potere del polimorfismo.

// Client usage:

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

...

Spero che questo aiuti.

Un'ultima nota sull'uso di List vs. ArrayList. È sempre meglio programmare sull'interfaccia piuttosto che sull'implementazione, in questo modo si rende il codice più flessibile. Puoi utilizzare un'altra implementazione di Elenco più veloce o fare qualcos'altro, senza modificare il codice client.

Altri suggerimenti

Statico è la cosa sbagliata da usare qui.

Concettualmente statico è sbagliato perché è solo per servizi che non corrispondono a un oggetto reale, fisico o concettuale. Hai un numero di tabelle e ognuna dovrebbe essere rappresentata da un oggetto reale nel sistema, non solo essere una classe. Sembra un po 'teorico ma ha conseguenze reali, come vedremo.

Ogni tabella appartiene a una classe diversa e va bene. Dato che puoi avere solo una di ogni tabella, limita il numero di istanze di ogni classe a una (usa un flag - non renderlo un Singleton). Fai in modo che il programma crei un'istanza della classe prima di accedere alla tabella.

Ora hai un paio di vantaggi. Puoi usare tutta la potenza dell'ereditarietà e dell'override poiché i tuoi metodi non sono più statici. È possibile utilizzare il costruttore per eseguire qualsiasi inizializzazione, inclusa l'associazione di SQL alla tabella (SQL che i metodi potranno utilizzare in seguito). Questo dovrebbe far scomparire tutti i tuoi problemi sopra, o almeno diventare molto più semplice.

Sembra che ci sia lavoro extra nel dover creare l'oggetto e memoria aggiuntiva, ma è davvero banale rispetto ai vantaggi. Alcuni byte di memoria per l'oggetto non verranno notati e una manciata di chiamate del costruttore impiegherà forse dieci minuti per aggiungere. Contro questo è il vantaggio che il codice per inizializzare qualsiasi tabella non deve essere eseguito se la tabella non viene utilizzata (il costruttore non dovrebbe essere chiamato). Lo troverai semplifica molto le cose.

Perché non usare le annotazioni? Si adattano perfettamente a ciò che stai facendo: aggiungere meta-informazioni (qui una query SQL) a una classe.

Come suggerito, è possibile utilizzare le annotazioni oppure spostare i metodi statici su oggetti factory:

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....."; }
}

Ma non è un buon odore avere oggetti senza alcun stato.

Se stai passando una classe per findAll, perché non puoi passare una classe a getSelectSQL in ModelBase?

asterite: vuoi dire che getSelectSQL esiste solo in ModelBase e usa la classe passata per creare un tablename o qualcosa del genere? Non posso farlo, perché alcuni dei Modelli hanno costrutti selettivi molto diversi, quindi non posso usare un universale " select * da " + classToTableName () ;. E qualsiasi tentativo di ottenere informazioni dai Modelli sul loro costrutto selezionato si imbatte nello stesso problema della domanda originale: è necessaria un'istanza del Modello o qualche riflessione elaborata.

gizmo: darò sicuramente un'occhiata alle annotazioni. Anche se non posso fare a meno di chiedermi cosa hanno fatto le persone con questi problemi prima che ci fosse la riflessione?

Potresti avere i tuoi metodi SQL come metodi di istanza in una classe separata.
Quindi passa l'oggetto modello al costruttore di questa nuova classe e chiama i suoi metodi per ottenere SQL.

Wow - questo è un esempio molto migliore di qualcosa che ho chiesto in precedenza in termini più generali - come implementare proprietà o metodi statici per ogni classe di implementazione in modo da evitare la duplicazione, fornire accesso statico senza dover istanziare la classe preoccupato e si sente "giusto".

Risposta breve (Java o .NET): impossibile. Risposta più lunga: puoi farlo se non ti dispiace usare un'annotazione a livello di classe (riflessione) o creare un'istanza di un oggetto (metodo di istanza) ma nessuno dei due è veramente "pulito".

Vedi la mia domanda (correlata) precedente qui: Come gestire i campi statici che variano implementando la classe Pensavo che le risposte fossero tutte davvero scadenti e ho perso il punto. La tua domanda è formulata molto meglio.

Sono d'accordo con Gizmo: stai guardando le annotazioni o una sorta di file di configurazione. Darei un'occhiata a Hibernate e ad altri framework ORM (e forse anche a librerie come log4j!) Per vedere come gestiscono il caricamento di meta-informazioni a livello di classe.

Non tutto può o dovrebbe essere fatto a livello di programmazione, penso che questo possa essere uno di quei casi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top