web-dev-qa-db-ja.com

データアクセス層のデザインパターン

データベース(MongoDB)を使用して情報を保存するアプリケーションがあります。過去に、静的メソッドでいっぱいのクラスを使用してデータを保存および取得しましたが、これはオブジェクト指向または将来の保証ではないことに気づきました。

データベースを変更する可能性は非常に低いですが、Mongoにあまり強く結び付けないものを使用したいと思います。また、データベースからキャッシュされたオブジェクトを更新するオプションを使用して結果をキャッシュできるようにしたいのですが、これは必須ではなく、別の場所で実行できます。

私はデータアクセスオブジェクトを見てきましたが、それらはあまり明確に定義されていないようで、実装の良い例を見つけることができません(Javaまたは同様の言語))私もたくさん持っていますタブ補完用のユーザー名を見つけるなど、適切ではないと思われ、DAOが大きく肥大化するような場合はオフです。

データベース固有でなくてもオブジェクトの取得と保存を容易にするデザインパターンはありますか?実装の良い例が役立つでしょう(できればJavaで)。

11
user2248702

さて、Javaでのデータストレージへの一般的なアプローチは、ご指摘のとおり、まったくオブジェクト指向ではありません。これは、それ自体が悪いことでも良いことでもありません。「オブジェクト指向」は利点でも欠点でもありません。これは多くのパラダイムの1つにすぎず、優れたアーキテクチャ設計に役立つ場合もあります(そうでない場合もあります)。

JavaのDAOが通常オブジェクト指向ではない理由は、まさにあなたが達成したいことです-データベース固有への依存を緩和します。より適切に設計された言語では、多重継承が可能になり、これは、もちろん、オブジェクト指向の方法で非常にエレガントに行うことができますが、Javaでは、それが価値があるよりも厄介なようです。

広い意味で、非オブジェクト指向アプローチは、アプリケーションレベルのデータを保存方法から切り離すのに役立ちます。これは、特定のデータベースの詳細への(非)依存性だけでなく、ストレージスキーマにも依存します。これは、リレーショナルデータベースを使用する場合に特に重要です(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()を変更して別のクラスのインスタンスを作成するだけで、すべてのアプリケーションコードが変更なしで新しいデータベースを使用します。

実際には、これはコードをまったく変更せずに行われることがよくあります。ファクトリメソッドはプロパティ設定を介して実装クラス名を取得し、リフレクションを使用してインスタンス化するため、実装を切り替えるために必要なのはプロパティを編集することだけです。ファイル。この「依存性注入」メカニズムを管理するフレームワーク(springguiceなど)が実際にありますが、実際には範囲を超えているため、最初に詳細には触れません。また、これらのフレームワークを使用することで得られるメリットは、ほとんどのアプリケーションでフレームワークと統合するのに苦労する価値があるとは必ずしも確信していないためです。

静的ではなく、この「ファクトリアプローチ」のもう1つの(おそらく、利用される可能性が高い)利点は、テスト容易性です。基礎となるDAOとは関係なく、Appクラスのロジックをテストする単体テストを作成していると想像してください。いくつかの理由(速度、セットアップ、あとがきのクリーンアップ、他のテストとの衝突の可能性、DAOの問題でテスト結果を汚染する可能性、App、実際にテストされているなど)。

これを行うには、Mockitoのような、任意のオブジェクトまたはメソッドの機能を「モックアウト」して、事前定義された動作を備えた「ダミー」オブジェクトに置き換えることができるテストフレームワークが必要です(詳細はスキップしてください。これも範囲を超えているためです)。したがって、DAOの代わりにこのダミーオブジェクトを作成し、テストの前にGreatDAOFactory.setDAO(dao)を呼び出して(そして後で復元して)、GreatDAOFactoryに本物の代わりにダミーを返すようにすることができます。インスタンスクラスの代わりに静的メソッドを使用していた場合、これは不可能です。

もう1つの利点は、上記で説明したデータベースの切り替えに似ていますが、追加機能を使用してdaoを「ポン引き」することです。データベース内のデータ量が増えるにつれてアプリケーションの速度が低下し、キャッシュレイヤーが必要であると判断したとします。ラッパークラスを実装します。これは、実際のdaoインスタンス(コンストラクターパラメーターとして提供される)を使用してデータベースにアクセスし、読み取ったオブジェクトをメモリにキャッシュして、より速く返されるようにします。次に、_GreatDAOFactory.getDAO_にこのラッパーをインスタンス化させて、アプリケーションがそれを利用できるようにします。

(これは「委任パターン」と呼ばれます...特にDAOで定義されたメソッドがたくさんある場合は、お尻が痛いようです。1つだけの動作を変更する場合でも、それらすべてをラッパーに実装する必要があります。または、daoをサブクラス化して、この方法でキャッシュを追加することもできます。これは、事前にコーディングを行うのはそれほど退屈ではありませんが、データベースを変更する場合、またはさらに悪いことに、実装を前後に切り替えます。)

「ファクトリ」メソッドの代わりに同様に広く使用されている(しかし、私の意見では劣っている)1つは、daoをそれを必要とするすべてのクラスのメンバー変数にすることです。

_public class App {
   GreatDao dao;
   public App(GreatDao d) { dao = d; }
}
_

このように、これらのクラスをインスタンス化するコードは、daoオブジェクトをインスタンス化し(ファクトリを引き続き使用できます)、コンストラクターパラメーターとして提供する必要があります。上記の依存性注入フレームワークは、通常、これと同様のことを行います。

これは、私が以前に説明した「ファクトリメソッド」アプローチのすべての利点を提供しますが、私が言ったように、私の意見ではそれほど良くありません。ここでの欠点は、アプリクラスごとにコンストラクターを作成する必要があること、まったく同じことを何度も繰り返すこと、必要なときにクラスを簡単にインスタンス化できないこと、読みやすさが失われることです。十分な大きさのコードベースが必要です。 、コードに精通していない読者は、daoの実際の実装が使用されているか、インスタンス化されているか、シングルトンであるか、スレッドセーフな実装であるか、状態を保持しているか、キャッシュであるかを理解するのに苦労します。何でも、特定の実装を選択する際の決定がどのように行われるかなど。

39
Dima