13

私が書いている Java プログラム用のミニ ORM を作成しています... データベース内の各テーブルのクラスがあり、すべて から継承していModelBaseます。

ModelBase抽象的であり、データベースからオブジェクトを見つけてバインドするための一連の静的メソッドを提供します。次に例を示します。

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 には静的メソッドのポリモーフィズムがないためです。getSelectSQL()しかし、インスタンス メソッドを作成したくありませんAlbum。実際に動作が静的な文字列を取得するためだけにインスタンスを作成する必要があるからです。

現時点でfindAll()は、リフレクションを使用して、問題のクラスに適切な sql を取得します。

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

しかし、それはかなりひどいです。

アイデアはありますか?これは、私が何度も何度も経験している一般的な問題です。クラスまたはインターフェイスで抽象静的メソッドを指定できないことです。静的メソッドのポリモーフィズムが機能しない、または機能しない理由はわかっていますが、それでももう一度使用したくなるわけではありません。

具象サブクラス X および Y がクラス メソッド (またはそれが失敗すると、クラス定数!) を実装することを保証できるパターン/構造はありますか?

4

9 に答える 9

4

ここで static を使用するのは間違っています。

概念的に静的というのは、物理的または概念的な実際のオブジェクトに対応しないサービス専用であるため、間違っています。多数のテーブルがあり、それぞれが単なるクラスではなく、システム内の実際のオブジェクトによって表される必要があります。これは少し理論的な話のように聞こえますが、後で説明するように、実際の結果があります。

各テーブルは異なるクラスであり、それで問題ありません。各テーブルは 1 つしか持てないため、各クラスのインスタンス数を 1 つに制限します (フラグを使用してください - シングルトンにしないでください)。プログラムがテーブルにアクセスする前に、クラスのインスタンスを作成するようにします。

これで、いくつかの利点があります。メソッドが静的でなくなるため、継承とオーバーライドを最大限に活用できます。コンストラクターを使用して、SQL をテーブルに関連付けるなどの初期化を行うことができます (後でメソッドで使用できる SQL)。これにより、上記の問題がすべて解消されるか、少なくともはるかに簡単になります。

オブジェクトを作成するための余分な作業と余分なメモリがあるように見えますが、利点に比べれば些細なことです。オブジェクト用の数バイトのメモリは認識されず、少数のコンストラクター呼び出しを追加するには 10 分程度かかる場合があります。それに対して、テーブルが使用されていない場合は、テーブルを初期化するコードを実行する必要がないという利点があります (コンストラクターを呼び出す必要はありません)。物事が大幅に簡素化されることがわかります。

于 2008-09-30T14:01:35.947 に答える
2

とはいえ、「静的はここで使用するのは間違っている」という点には完全に同意しますが、ここで対処しようとしていることはある程度理解しています。それでもインスタンスの動作が機能するはずですが、これが私が行うことだと主張する場合:

あなたのコメントから始めて、「動作が本当に静的な文字列を取得するためだけに、そのインスタンスを作成する必要があります」

それは完全に正しいわけではありません。よく見ると、基本クラスの動作を変更しているのではなく、メソッドのパラメーターを変更しているだけです。つまり、アルゴリズムではなく、データを変更しています。

新しいサブクラスがメソッドの動作方法を変更したい場合、継承はより便利です。クラスが動作するために使用する「データ」を変更する必要がある場合は、おそらくこのようなアプローチがうまくいくでしょう。

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;

    }
}

つまり、すべてのステートメントを Map でマップします。これに対する「明らかな」次のステップは、プロパティ ファイル、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 の使用に関する最後の注意事項。実装よりもインターフェイスに対してプログラミングする方が常に優れています。このようにして、コードをより柔軟にすることができます。クライアント コードを変更せずに、より高速な別の List 実装を使用するか、別のことを行うことができます。

于 2008-09-30T17:17:25.320 に答える
1

なぜ注釈を使用しないのですか? これらは、メタ情報 (ここでは SQL クエリ) をクラスに追加するという、あなたが行っていることと非常によく合います。

于 2008-09-30T04:49:07.817 に答える
1

提案されているように、注釈を使用するか、静的メソッドをファクトリ オブジェクトに移動することができます。

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

しかし、状態のないオブジェクトを持つことはあまり良い匂いではありません。

于 2008-09-30T08:04:09.783 に答える
0

SQL メソッドを別のクラスのインスタンス メソッドとして持つことができます。
次に、モデル オブジェクトをこの新しいクラスのコンストラクターに渡し、そのメソッドを呼び出して SQL を取得します。

于 2008-09-30T05:45:33.673 に答える
0

Class を findAll に渡す場合、ModelBase の getSelectSQL にクラスを渡せないのはなぜですか?

于 2008-09-30T04:48:08.460 に答える
0

うわー-これは、より一般的な用語で以前に尋ねたことのはるかに良い例です-重複を回避し、関係するクラスをインスタンス化する必要なく静的アクセスを提供し、感じる方法で、各実装クラスに静的なプロパティまたはメソッドを実装する方法'右'。

簡単な回答 (Java または .NET): できません。より長い答え-クラスレベルの注釈(リフレクション)またはオブジェクトのインスタンス化(インスタンスメソッド)を使用しても構わないが、どちらも真に「クリーン」ではない場合は可能です。

ここで私の以前の(関連する)質問を参照してください:実装クラスによって異なる静的フィールドを処理する方法 答えはすべて本当に不十分で、要点を逃したと思いました。あなたの質問ははるかに良い言葉です。

于 2008-09-30T07:39:55.567 に答える
0

asterite: getSelectSQL は ModelBase にのみ存在し、渡されたクラスを使用してテーブル名などを作成するということですか? 一部のモデルには非常に異なる選択構造があるため、それはできません。そのため、ユニバーサルな「select * from」+ classToTableName(); を使用できません。そして、select コンストラクトに関するモデルから情報を取得しようとすると、元の質問と同じ問題が発生します。モデルのインスタンスまたは派手なリフレクションが必要です。

ギズモ: 私は間違いなく注釈を調べます。反省する前に、人々はこれらの問題に対して何をしたのだろうかと思わずにはいられませんが?

于 2008-09-30T05:26:12.877 に答える
-1

ギズモに同意します。注釈または何らかの構成ファイルを見ているのです。Hibernate やその他の ORM フレームワーク (さらには log4j などのライブラリも!) を調べて、それらがクラスレベルのメタ情報の読み込みをどのように処理するかを確認します。

すべてをプログラムで実行できるわけではなく、実行する必要があるわけでもありません。

于 2008-09-30T06:13:48.440 に答える