質問
私が書いているJavaプログラムのミニORMを作成しています... dbの各テーブルにクラスがあり、すべてが ModelBase
を継承しています。
ModelBase
は抽象的です& &を見つけるための多数の静的メソッドを提供します。 dbからオブジェクトをバインドします。例:
public static ArrayList findAll(Class cast_to_class) {
//build the sql query & execute it
}
したがって、 ModelBase.findAll(Albums.class)
のような操作を行って、すべての永続アルバムのリストを取得できます。
私の問題は、この静的なコンテキストで、具体的なクラスAlbumから適切なSQL文字列を取得する必要があることです。
public class Album extends ModelBase {
public static String getSelectSQL() { return "select * from albums.....";}
}
Javaの静的メソッドには多態性がないため。しかし、 Album
で getSelectSQL()
をインスタンスメソッドにしたくないのは、実際に静的な文字列を取得するためだけにインスタンスを作成する必要があるためです。動作。
現時点では、 findAll()
はリフレクションを使用して、問題のクラスに適切なsqlを取得します。
select_sql = (String)cast_to_class.getDeclaredMethod("getSelectSql", new Class[]{} ).invoke(null, null);
しかし、それはかなりひどいです。
では、アイデアはありますか?これは、私が何度も経験している一般的な問題です。クラスまたはインターフェースで抽象静的メソッドを指定できないことです。静的メソッドポリモーフィズムが機能せず、機能しないことをなぜ知っていますが、それが再びそれを使用したいと思うことを止めません!
具体的なサブクラスXおよびYがクラスメソッドを実装することを保証できるパターン/構造はありますか(または、それが失敗するとクラス定数です!)
解決
とはいえ、「静的はここで使用するのは間違っている」という点には完全に同意しますが、ここで対処しようとしていることを理解しています。それでもインスタンスの振る舞いが機能するはずですが、もしあなたがこれを私がすることを主張するなら:
コメントから開始"実際に静的な振る舞いの文字列を取得するために、そのインスタンスを作成する必要があります"
完全に正しいわけではありません。よく見ると、メソッドのパラメーターを変更するだけで、基本クラスの動作は変更されていません。つまり、アルゴリズムではなくデータを変更します。
継承は、新しいサブクラスがメソッドの動作方法を変更する場合に便利です。「データ」を変更するだけでよい場合。クラスは、おそらくこのようなアプローチを使用して動作します。
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;
}
}
つまり、すべてのステートメントをマップにマップします。 「明白な」これへの次のステップは、柔軟性を高めるために、プロパティファイルやXMLなどの外部リソースからデータベースをロードすることです。
この方法では、「インスタンスを作成する」必要がないため、クラスのクライアント(および自分自身)を幸せに保つことができます。仕事をする。
// Client usage:
...
List albums = ModelBase.findAll( Album.class );
...
もう1つの方法は、インスタンスを背後から作成し、インスタンスメソッドを使用している間、クライアントインターフェースをそのまま保持することです。外部呼び出しを避けるため。前のサンプルと同様の方法で、これを行うこともできます
// 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";
}
}
そして、クライアントコードを変更する必要はなく、ポリモーフィズムの力もあります。
// Client usage:
...
List albums = ModelBase.findAll( Album.class ); // Does not know , behind the scenes you use instances.
...
これが役立つことを願っています。
ListとArrayListの使用に関する最後の注意。実装よりもインターフェイスにプログラムする方が常に良いので、コードをより柔軟にします。クライアントコードを変更せずに、より高速な別のリスト実装を使用したり、他のことを実行したりできます。
他のヒント
静的は、ここで使用するのが間違っています。
静的な概念は、実際のオブジェクト(物理的または概念的)に対応していないサービス専用であるため、間違っています。多数のテーブルがあり、各クラスはクラスではなく、システム内の実際のオブジェクトによって表される必要があります。それは少し理論的なように聞こえますが、後で見るように実際の結果があります。
各テーブルは異なるクラスのものであり、それでかまいません。各テーブルは1つしか持てないため、各クラスのインスタンスの数を1つに制限します(フラグを使用します-シングルトンにしないでください)。テーブルにアクセスする前に、プログラムにクラスのインスタンスを作成させます。
ここで、いくつかの利点があります。メソッドは静的ではないため、継承とオーバーライドのすべての機能を使用できます。コンストラクターを使用して、SQLとテーブル(メソッドが後で使用できるSQL)の関連付けなど、初期化を行うことができます。これにより、上記のすべての問題が解消されるか、少なくともはるかに簡単になります。
オブジェクトを作成するために余分な作業と余分なメモリがあるように見えますが、利点と比較して本当に簡単です。オブジェクトの数バイトのメモリは気づかれず、ほんの一握りのコンストラクターの呼び出しは追加するのにおそらく10分かかります。それに対して、テーブルを使用しない場合は、テーブルを初期化するコードを実行する必要がないという利点があります(コンストラクターを呼び出さないでください)。物事が非常に単純化されることがわかるでしょう。
注釈を使用しないのはなぜですか?メタ情報(ここではSQLクエリ)をクラスに追加するために、あなたが何をしているのかかなりよく適合しています。
提案されているように、注釈を使用するか、静的メソッドをファクトリオブジェクトに移動できます。
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....."; }
}
しかし、状態のないオブジェクトを持つことはあまり良い匂いではありません。
findAllにクラスを渡す場合、ModelBaseのgetSelectSQLにクラスを渡せないのはなぜですか?
asterite:getSelectSQLはModelBaseにのみ存在し、渡されたクラスを使用してテーブル名などを作成しますか? 一部のモデルには非常に異なる選択構造があるため、汎用の&quot; select * from&quot;を使用することはできません。 + classToTableName();。また、モデルからselectコンストラクトに関する情報を取得しようとすると、元の質問と同じ問題が発生します。モデルのインスタンスまたは派手なリフレクションが必要です。
gizmo:アノテーションについて詳しく見ていきます。反省が起こる前に、これらの問題で人々が何をしたのか不思議に思わずにはいられませんか?
SQLメソッドを別のクラスのインスタンスメソッドとして使用できます。
次に、モデルオブジェクトをこの新しいクラスのコンストラクターに渡し、そのメソッドを呼び出してSQLを取得します。
すごい-これは以前より一般的な用語で私が尋ねたもののはるかに良い例です-重複を避ける方法で各実装クラスに静的なプロパティまたはメソッドを実装する方法、クラスをインスタンス化することなく静的アクセスを提供します懸念し、「正しい」と感じています。
簡単な回答(Javaまたは.NET):できません。 より長い答え-クラスレベルのアノテーション(リフレクション)またはオブジェクトのインスタンス化(インスタンスメソッド)を使用してもかまいませんが、どちらも本当に「クリーン」ではない場合は可能です。
以前の(関連する)質問はこちらをご覧ください:クラスの実装によって異なる静的フィールドの処理方法 私は答えがすべて本当に足りないと思い、その点を見落としました。あなたの質問ははるかに良い言葉で表現されています。
Gizmoに同意します。注釈または何らかの構成ファイルを見ていることになります。 Hibernateと他のORMフレームワーク(および場合によってはlog4jなどのライブラリも参照)を見て、クラスレベルのメタ情報の読み込みを処理する方法を確認します。
すべてをプログラムで実行できる、または実行する必要があるわけではありません。これはそのようなケースの1つであると思われます。