2008-09-30 10 views
13

Ich mache ein Mini-ORM für ein Java-Programm Ich schreibe ... Es gibt eine Klasse für jede Tabelle in meiner Datenbank, alle erben von.Alternativen zu statischen Methoden in Java

ModelBase ist abstrakt & eine Reihe von statischen Methoden liefert & Bindungs ​​Objekte aus der db für die Suche, zum Beispiel:

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

So kann man Dinge wie ModelBase.findAll(Albums.class) tun, um eine Liste aller Alben beibehalten zu bekommen. Mein Problem ist, dass ich in diesem statischen Kontext die entsprechende SQL-Zeichenfolge aus dem konkreten Klassenalbum erhalten muss. Ich kann keine statische Methode wie

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

haben, da es keinen polymorphism für statische Methoden in Java gibt. Aber ich möchte nicht getSelectSQL() eine Instanz Methode in Album machen, denn dann muss ich eine Instanz davon erstellen, nur um eine Zeichenfolge zu erhalten, die wirklich im Verhalten ist.

Im Moment findAll() verwendet Reflektion, die entsprechende SQL für die Klasse in Frage zu bekommen:

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

Aber das ist ziemlich eklig.

Also irgendwelche Ideen? Es ist ein generelles Problem, das ich immer wieder habe - die Unfähigkeit abstrakte statische Methoden in Klassen oder Interfaces anzugeben. Ich weiß warum statische Methode Polymorphismus nicht und kann nicht funktionieren, aber das hält mich nicht davon ab, es wieder mal zu verwenden!

Gibt es ein Muster/Konstrukt, mit dem ich sicherstellen kann, dass die konkreten Unterklassen X und Y eine Klassenmethode implementieren (oder andernfalls eine Klassenkonstante!)?

+0

Ich bin ein wenig verwirrt, warum Sie Ihre eigenen ORM schreiben, wenn es hervorragende Möglichkeiten wie iBatis und Hibernate gibt. Übrigens scheint es mir, dass Sie versuchen, eine statische Java-Methode wie eine Ruby-Klassenmethode funktionieren zu lassen. Wenn ja, wird es nicht funktionieren :( – Alan

Antwort

2

Obwohl ich absolut stimme in dem Punkt "Statische ist die falsche Sache hier zu verwenden", verstehe ich irgendwie, was Sie hier ansprechen wollen. Immer noch Instanz-Verhalten sollte die Möglichkeit zu arbeiten, aber wenn Sie darauf bestehen, ist das, was ich tun würde:

Ausgehend von Ihrem Kommentar "Ich muss eine Instanz erstellen, nur um eine Zeichenfolge zu erhalten, die wirklich im Verhalten ist"

Es ist nicht vollständig richtig. Wenn Sie gut aussehen, ändern Sie nicht das Verhalten Ihrer Basisklasse, sondern ändern nur den Parameter für eine Methode. Mit anderen Worten, Sie ändern die Daten, nicht den Algorithmus.

Vererbung ist nützlicher, wenn eine neue Unterklasse die Funktionsweise einer Methode ändern möchte. Wenn Sie nur die "Daten" ändern müssen, die die Klasse zur Arbeit verwendet, würde wahrscheinlich ein solcher Ansatz den Trick machen.

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; 

    } 
} 

Das heißt, alle Anweisungen mit einer Karte zuordnen. Der "naheliegende" nächste Schritt ist das Laden der Map aus einer externen Ressource, z. B. eine Eigenschaftendatei, oder eine XML-Datei oder sogar (warum nicht) eine Datenbanktabelle für zusätzliche Flexibilität.

Auf diese Weise können Sie Ihre Klassenkunden (und sich selbst) glücklich machen, weil Sie nicht "eine Instanz erstellen" müssen, um die Arbeit zu erledigen.

// Client usage: 

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

...

Ein weiterer Ansatz ist die Instanzen von hinten zu schaffen, und halten Sie Ihre Client-Schnittstelle intakt, während Instanzmethoden verwenden, werden die Methoden als „geschützt“ gekennzeichnet externen Aufruf zu vermeiden ist. Auf ähnliche Art und Weise der vorherigen Probe können Sie dies auch

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

Und Sie brauchen nicht den Client-Code zu ändern, und immer noch die Macht der Polymorphie haben.

// Client usage: 

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

...

Ich hoffe, das hilft.

Eine letzte Anmerkung zur Verwendung von List vs. ArrayList. Es ist immer besser, auf die Schnittstelle als auf die Implementierung zu programmieren, damit Sie Ihren Code flexibler gestalten. Sie können eine andere List-Implementierung verwenden, die schneller ist oder etwas anderes tut, ohne den Client-Code zu ändern.

0

Wenn Sie eine Klasse an findAll übergeben, warum können Sie in ModelBase keine Klasse an getSelectSQL übergeben?

1

Warum verwenden Sie keine Anmerkungen? Sie passen sehr gut, was Sie tun: Meta-Informationen (hier eine SQL-Abfrage) zu einer Klasse hinzufügen.

0

asterit: meinst du, dass getSelectSQL nur in ModelBase existiert, und es verwendet die übergebene Klasse, um einen Tabellennamen oder etwas ähnliches zu machen? Ich kann das nicht tun, weil einige der Modelle wildly unterschiedliche Auswahl Konstrukte haben, so kann ich nicht verwenden eine universelle "select * from" + classToTableName() ;. Und jeder Versuch, Informationen von den Modellen über ihr ausgewähltes Konstrukt zu bekommen, läuft auf das gleiche Problem von der ursprünglichen Frage hinaus - Sie benötigen eine Instanz des Modells oder eine besondere Reflexion.

gizmo: Ich werde definitiv in Annotationen schauen. Obwohl ich nicht helfen kann, mich aber zu fragen, was die Leute mit diesen Problemen gemacht haben, bevor es Überlegungen gab?

+0

Dann sollten Sie sich ORM in Sprachen anschauen, die keine Reflektion unterstützen, wie zB C++. Denn soweit ich weiß, werden alle Java ORM (wenn nicht alle) Reflektionen verwendet Irgendwann .. – gizmo

+0

Bitte machen Sie diese Kommentare als ... Kommentare;) Eine Antwort, wenn nur zur Antwort. Auf diese Weise bearbeiten Gizmo und Asterite * ihre Antworten, um sie zu vervollständigen, oder hinterlassen weitere Kommentare, um die von Ihren Fragen initiierte Diskussion fortzusetzen – VonC

0

Sie könnten Ihre SQL-Methoden als Instanzmethoden in einer separaten Klasse haben.
Dann übergeben Sie das Modellobjekt in den Konstruktor dieser neuen Klasse und rufen Sie seine Methoden zum Abrufen von SQL.

-1

Ich stimme Gizmo zu: Sie betrachten entweder Anmerkungen oder irgendeine Art von Konfigurationsdatei. Ich würde mir Hibernate und andere ORM-Frameworks (und vielleicht sogar Bibliotheken wie log4j!) Ansehen, um zu sehen, wie sie mit dem Laden von Meta-Informationen auf Klassenebene umgehen.

Nicht alles kann oder sollte programmgesteuert durchgeführt werden, ich denke, das könnte einer dieser Fälle sein.

0

Wow - das ist ein weit besseres Beispiel für etwas, das ich zuvor in allgemeineren Begriffen gefragt habe - wie Eigenschaften oder Methoden, die Statisch zu jeder implementierenden Klasse sind, auf eine Art und Weise zu implementieren, die Doppelarbeit vermeidet, statischen Zugriff ermöglicht, ohne die Instanz instanziieren zu müssen Klasse betroffen und fühlt sich "richtig".

Kurze Antwort (Java oder .NET): Sie können nicht. Längere Antwort - Sie können, wenn es Ihnen nichts ausmacht, eine Annotation auf Klassenebene (Reflexion) oder ein Objekt (Instanzmethode) zu instanziieren, aber beide sind nicht wirklich "sauber".

Siehe meine vorherige (bezogen) Frage hier: How to handle static fields that vary by implementing class Ich dachte, die alle Antworten wirklich lahm waren und den Punkt verpasst. Ihre Frage ist viel besser formuliert.

1

Wie bereits angedeutet, können Sie Anmerkungen verwenden, oder Sie können die statischen Methoden auf die Werks Objekte bewegen:

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

Aber es ist kein sehr guter Geruch Objekte ohne Staat.

4

Statisch ist die falsche Sache hier zu verwenden.

Konzeptionell statisch ist falsch, weil es nur für Dienste ist, die nicht einem tatsächlichen, physischen oder konzeptuellen Objekt entsprechen. Sie haben eine Anzahl von Tabellen, und jede sollte durch ein tatsächliches Objekt im System repräsentiert werden, nicht nur eine Klasse. Das hört sich an, als wäre es ein bisschen theoretisch, aber es hat tatsächliche Konsequenzen, wie wir sehen werden.

Jede Tabelle ist von einer anderen Klasse, und das ist in Ordnung. Da Sie immer nur eine von jeder Tabelle haben können, begrenzen Sie die Anzahl der Instanzen jeder Klasse auf eins (benutzen Sie ein Flag - machen Sie es nicht zu einem Singleton). Veranlassen Sie, dass das Programm eine Instanz der Klasse erstellt, bevor es auf die Tabelle zugreift.

Jetzt haben Sie ein paar Vorteile. Sie können die volle Macht der Vererbung und des Überschreibens nutzen, da Ihre Methoden nicht mehr statisch sind. Sie können den Konstruktor verwenden, um eine Initialisierung durchzuführen, einschließlich der Zuordnung von SQL zur Tabelle (SQL, die Ihre Methoden später verwenden können). Dies sollte alle Ihre Probleme oben verschwinden lassen, oder zumindest viel einfacher werden.

Es scheint, als ob es zusätzliche Arbeit bei der Erstellung des Objekts und zusätzliche Speicher gibt, aber es ist wirklich trivial im Vergleich zu den Vorteilen. Ein paar Bytes Speicher für das Objekt werden nicht bemerkt, und eine Handvoll Konstruktoraufrufe brauchen vielleicht zehn Minuten zum Hinzufügen. Dagegen ist der Vorteil, dass Code, um irgendwelche Tabellen zu initialisieren, nicht ausgeführt werden muss, wenn die Tabelle nicht verwendet wird (der Konstruktor sollte nicht aufgerufen werden). Sie werden feststellen, dass es die Dinge sehr vereinfacht.

Verwandte Themen