2014-12-31 13 views
8

私はデータベース(MongoDB)を使って情報を保存するアプリケーションを持っています。過去には、データを保存して取得するために静的メソッドを使用したクラスを使用しましたが、これはオブジェクト指向のものではなく、将来の証明ではないことを認識しました。データアクセス層のデザインパターン

私はデータベースを変更することはほとんどありませんが、私はMongoに私をあまりにも強く結びつけないものを望みます。キャッシュされたオブジェクトをデータベースからリフレッシュするオプションを使用して結果をキャッシュすることもできますが、これは必須ではなく、別の場所で実行できます。

私はデータアクセスオブジェクトを見てきましたが、あまり定義されていないようで、実装の良い例は見つかりませんでした(Javaなど)。私はまた、タブの補完のためのユーザ名を見つけることなど多くの場合がありますが、それはうまくいかず、DAOを大きくして肥大化させてしまいます。

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

答えて

33

Javaでのデータストレージの一般的なアプローチは、あなたが指摘したように、まったくオブジェクト指向ではありません。 「オブジェクト指向性」は長所でも短所でもなく、時には優れたアーキテクチャ設計に役立つ(時にはそうではない)多くのパラダイムの1つにすぎません。

JavaのDAOが通常オブジェクト指向ではないのは、データベース固有の依存関係を緩和するためです。複数の継承が可能な、より設計された言語では、これはもちろん、オブジェクト指向の方法で非常にエレガントに行うことができますが、Javaでは価値があるよりも面倒です。

より広い意味で、非OOアプローチは、アプリケーションレベルのデータを保存方法から切り離すのに役立ちます。これは特定のデータベースの仕様に依存するだけでなく、リレーショナルデータベースを使用する際に特に重要なストレージスキーマ(ORMで私を起動させないでください)、つまり、設計されたリレーショナルスキーマを持つことができますDAOによってアプリケーションのOOモデルにシームレスに変換されます。

だから、今日のJavaのほとんどのDAOは基本的には初めに言及したものです - クラス、静的メソッドがいっぱいです。 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つ(おそらく、より有利に利用される可能性があります)。あなたがAppクラスのロジックを基になるDAOから独立してテストすべき単体テストを書いていると想像してください。 (スピード、セットアップの必要性、後のクリーンアップ、他のテストとの衝突の可能性、DAOの問題を伴う恐れのあるテスト結果の可能性、Appとは無関係。実際にテストされているなど)。

Mockitoのようなテストフレームワークを使用して、任意のオブジェクトまたはメソッドの機能を "模造"し、あらかじめ定義された動作を持つ "ダミー"オブジェクトに置き換えることができます。詳細は、これもまた範囲外です)。したがって、このダミーオブジェクトを作成してDAOを置き換えることができます。は、テストの前にGreatDAOFactory.setDAO(dao)を呼び出すことで、実際のものの代わりにダミーを返します(後で復元します)。インスタンスクラスの代わりに静的メソッドを使用していた場合、これは不可能です。

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

(これは「デリゲーションパターン」と呼ばれます...あなたのDAOで多くのメソッドが定義されているときは、バットの痛みのようです:変更する場合でも、すべてをラッパーに実装する必要がありますまた、あなたは単にDAOをサブクラス化して、このようにキャッシュを追加することもできます。これは、あまり退屈ではないコードコーディングであるかもしれませんが、データベースを変更しようとすると問題になるかもしれません。実装を前後に切り替えるオプションがあります)。

一つが均等に広く使用され(しかし、私の意見では、劣っ)「工場」の方法に代わるものではdaoそれを必要とするすべてのクラスのメンバ変数を作っている:

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

この方法は、コードをこれらのクラスをインスタンス化するインスタンスは、daoオブジェクトをインスタンス化する必要があり(ファクトリを使用することもできます)、それをコンストラクタパラメータとして提供する必要があります。私が上で述べた依存性注入フレームワークは、通常、これに似た何かをします。

これは私が先に述べた「ファクトリメソッド」アプローチのすべての利点を提供しますが、私が言ったように、私の意見ではそれほど良くありません。ここでの短所は、それぞれのアプリケーションクラスのコンストラクタを書くこと、同じことを何度もやり直すこと、そして必要に応じてクラスを簡単にインスタンス化できないこと、読みやすさが失われてしまうことです。あなたのコードを読んで、それに精通していない人は、daoの実際の実装がどのように使われているのか、それがどのようにインスタンス化されているのか、それがシングルトンであろうとスレッドセーフな実装であろうと、何か、特定の実装の選択に関する決定がどのように行われるかなど

+0

私は実際には、ディスク/データベースへの永続性が成功したことを示すために、保存/削除操作のために '' 'boolean''を返します。 – ambodi

+1

Nahさん、とにかく失敗した場合は、例外をスローしたいと思っています。これは、単にfalseを返すのと比べて、呼び出し元に戻って失敗に関する多くの貴重な情報を伝えることを可能にし、また、 :すべての呼び出しの状態をチェックして、スタック全体に伝播するのではなく、処理が必要なレベルで一度しか例外を捕捉する必要がありません。 – Dima

+1

これは過小評価された回答です。 1つの疑問は、 'setDAO'が何も返さないのではなく、古い/以前のDAOを返すのはなぜですか?それはどのように使用されますか? –

関連する問題