さて、あなたが指摘したように、Javaでのデータストレージへの一般的なアプローチは、まったくオブジェクト指向ではありません。これはそれ自体が悪いことでも良いことでもありません。「オブジェクト指向」は長所でも短所でもありません。これは多くのパラダイムの1つにすぎず、優れたアーキテクチャ設計に役立つ場合もありますが、そうでない場合もあります。
JavaのDAOが通常オブジェクト指向ではない理由は、まさにあなたが達成したいことです-データベース固有への依存を緩和します。多重継承を可能にするより適切に設計された言語では、これ、またはもちろん、オブジェクト指向の方法で非常にエレガントに行うことができますが、Javaでは、それは価値があるよりも厄介なようです。
広い意味で、非OOアプローチは、アプリケーションレベルのデータを保存方法から切り離すのに役立ちます。これは、特定のデータベースの詳細への(非)依存性だけでなく、ストレージスキーマにも依存します。これは、リレーショナルデータベースを使用する場合に特に重要です(ORMを開始しないでください)。適切に設計されたリレーショナルスキーマを使用できます。 DAOによってアプリケーションOOモデルにシームレスに変換されます。
したがって、最近のほとんどのDAOがJavaに含まれているのは、基本的に最初に述べたものです。つまり、静的メソッドでいっぱいのクラスです。 1つの違いは、すべてのメソッドを静的にするのではなく、特定のインターフェイスを実装するDAOの(シングルトン)インスタンスを返す単一の静的な「ファクトリメソッド」(おそらく別のクラス)を使用する方がよいことです。 、データベースにアクセスするためにアプリケーションコードによって使用されます:
public interface GreatDAO {
User getUser(int id);
void saveUser(User u);
}
public class TheGreatestDAO implements GreatDAO {
protected TheGeatestDAO(){}
...
}
public class GreatDAOFactory {
private static GreatDAO dao = null;
protected static synchronized GreatDao setDAO(GreatDAO d) {
GreatDAO old = dao;
dao = d;
return old;
}
public static synchronized GreatDAO getDAO() {
return dao == null ? dao = new TheGreatestDAO() : dao;
}
}
public class App {
void setUserName(int id, String name) {
GreatDAO dao = GreatDAOFactory.getDao();
User u = dao.getUser(id);
u.setName(name);
dao.saveUser(u);
}
}
静的メソッドとは対照的に、なぜこのようにするのですか?さて、別のデータベースに切り替えることにした場合はどうなりますか?当然、新しいDAOクラスを作成し、新しいストレージのロジックを実装します。静的メソッドを使用している場合は、すべてのコードを調べてDAOにアクセスし、新しいクラスを使用するように変更する必要があります。これは大きな苦痛かもしれません。そして、気が変わって古いデータベースに戻したい場合はどうでしょうか。
このアプローチでは、GreatDAOFactory.getDAO()
を変更するだけです。 別のクラスのインスタンスを作成するようにすると、すべてのアプリケーションコードが変更なしで新しいデータベースを使用するようになります。
実際には、これはコードをまったく変更せずに行われることがよくあります。ファクトリメソッドはプロパティ設定を介して実装クラス名を取得し、リフレクションを使用してインスタンス化するため、実装を切り替えるために必要なのはプロパティを編集することだけです。ファイル。実際には、spring
のようなフレームワークがあります またはguice
-これはあなたのためにこの「依存性注入」メカニズムを管理しますが、最初に詳細には立ち入りません。なぜなら、それはあなたの質問の範囲を本当に超えているからです。また、あなたが使用することから得られる利益を必ずしも確信していないからです。これらのフレームワークは、ほとんどのアプリケーションでそれらと統合するのに苦労する価値があります。
静的とは対照的に、この「工場アプローチ」のもう1つの(おそらく、利用される可能性が高い)利点は、妥当性です。 App
のロジックをテストする単体テストを作成していると想像してみてください。 基礎となるDAOとは独立したクラス。いくつかの理由(速度、セットアップ、後書きのクリーンアップ、他のテストとの衝突の可能性、DAOの問題でテスト結果を汚染する可能性、App
、実際にテストされているなど)。
これを行うには、Mockito
のようなテストフレームワークが必要です。 、これにより、任意のオブジェクトまたはメソッドの機能を「モックアウト」して、事前定義された動作を持つ「ダミー」オブジェクトに置き換えることができます(これも範囲を超えているため、詳細はスキップします)。したがって、DAOの代わりにこのダミーオブジェクトを作成し、GreatDAOFactory
を作成できます。 GreatDAOFactory.setDAO(dao)
を呼び出して、本物ではなくダミーを返します テストの前(およびテスト後に復元)。インスタンスクラスの代わりに静的メソッドを使用している場合、これは不可能です。
もう1つの利点は、上記で説明したデータベースの切り替えに似ていますが、追加機能を使用してdaoを「ポン引き」することです。データベース内のデータ量が増えるにつれてアプリケーションの速度が低下し、キャッシュレイヤーが必要であると判断したとします。ラッパークラスを実装します。これは、実際のdaoインスタンス(コンストラクターパラメーターとして提供される)を使用してデータベースにアクセスし、読み取ったオブジェクトをメモリにキャッシュして、より速く返されるようにします。次に、GreatDAOFactory.getDAO
を作成できます。 このラッパーをインスタンス化して、アプリケーションがそれを利用できるようにします。
(これは「委任パターン」と呼ばれます...特にDAOで多くのメソッドが定義されている場合は、お尻が痛いようです。1つだけの動作を変更する場合でも、それらすべてをラッパーに実装する必要があります。または、daoをサブクラス化して、この方法でキャッシュを追加することもできます。これは、事前にコーディングを行うのはそれほど退屈ではありませんが、データベースを変更する場合、またはさらに悪いことに、実装を前後に切り替えます。)
「ファクトリ」メソッドの代わりに同様に広く使用されている(しかし、私の意見では劣っている)1つは、dao
を作成することです。 それを必要とするすべてのクラスのメンバー変数:
public class App {
GreatDao dao;
public App(GreatDao d) { dao = d; }
}
このように、これらのクラスをインスタンス化するコードは、daoオブジェクトをインスタンス化し(ファクトリを引き続き使用できます)、コンストラクターパラメーターとして提供する必要があります。上記の依存性注入フレームワークは、通常、これと同様のことを行います。
これは、私が以前に説明した「ファクトリメソッド」アプローチのすべての利点を提供しますが、私が言ったように、私の意見ではそれほど良くありません。ここでの欠点は、アプリクラスごとにコンストラクターを作成する必要があること、まったく同じことを何度も繰り返すこと、必要なときにクラスを簡単にインスタンス化できないこと、読みやすさが失われることです。コードベースが十分に大きい場合、コードに精通していない読者は、daoの実際の実装が使用されているか、インスタンス化されているか、シングルトンであるか、スレッドセーフな実装であるか、状態を保持するか、キャッシュするかを理解するのに苦労します。何でも、特定の実装を選択する際の決定がどのように行われるかなど。