デザインパターンWebベースアプリケーション[終了]


359

シンプルなWebベースのアプリケーションを設計しています。私はこのWebベースのドメインに不慣れです。サーブレット間で責任をどのように分散すべきか、新しいサーブレットを作成するための基準などの設計パターンについて、あなたのアドバイスが必要でした。

実際、私は自分のホームページにいくつかのエンティティがあり、それらのそれぞれに対応して、追加、編集、削除などのいくつかのオプションがあります。以前は、add entity1のServlet1、編集entity1のServlet2などのオプションごとに1つのサーブレットを使用していたため、このようにして多数のサーブレットが作成されていました。

今、私たちはデザインを変えています。私の質問は、サーブレットの責任をどのように選択するかを正確に選択する方法です。すべてのオプションを処理し、リクエストをサービス層に転送するエンティティごとに1つのサーブレットが必要です。または、ページ全体のリクエストを処理し、それを対応するサービス層に転送する1つのサーブレットをページ全体に割り当てる必要がありますか?また、リクエストオブジェクトがサービスレイヤーに転送されるかどうか。


8
実際には公式のデザインパターンではありませんが、PRG(post-redirect-get)とHijax(最初にjsを使用せずに作業し、次にリンクとボタンをajaxでハイジャックします)を忘れないでください
Neil McGuigan

回答:


488

少しまともなWebアプリケーションは、さまざまなデザインパターンで構成されています。最も重要なものだけを取り上げます。


モデルビューコントローラーパターン

使用するコア(アーキテクチャー)設計パターンは、Model-View-Controllerパターンです。コントローラは、直接特定の用途/作成(IN)サーブレットで表されるべきであるモデルビューの要求に基づい。モデルのJavaBeanクラスで表現されます。これは多くの場合、アクション(動作)を含むビジネスモデルとデータ(情報)を含むデータモデルでさらに分割できます。ビューは、(に直接アクセスしていJSPファイルで表現されるデータモデル EL(式言語)によると。

次に、アクションとイベントの処理方法に基づいてバリエーションがあります。人気のあるものは次のとおりです。

  • リクエスト(アクション)ベースのMVC:これは実装が最も簡単です。(ビジネスモデルはオブジェクトHttpServletRequestと直接連携しHttpServletResponseます。リクエストパラメータを収集し、変換し、検証する必要があります(ほとんどの場合)。ビューは、プレーンバニラのHTML / CSS / JSで表すことができ、それはリクエスト間で状態を維持しません。これが、Spring MVCStruts、およびStripesの動作方法です。

  • コンポーネントベースのMVC:これは実装が困難です。しかし、最終的には、すべての「生の」サーブレットAPIが完全に抽象化された、より単純なモデルとビューになります。リクエストパラメータを自分で収集、変換、検証する必要はありません。コントローラは、このタスクとセットで集められ、変換され、検証リクエストパラメータんモデル。必要なのは、モデルプロパティを直接操作するアクションメソッドを定義することだけです。のビュー今度はHTML / CSS / JSを生成するJSPタグライブラリまたはXML要素の風味で「コンポーネント」で表されます。の状態ビュー後続のリクエストはセッションで維持されます。これは、サーバー側の変換、検証、および値変更イベントに特に役立ちます。これがJSFWicketPlayなどの方法です。動作します。

補足として、自家製のMVCフレームワークを趣味で使うことは非常に素晴らしい学習課題であり、個人的またはプライベートな目的でそれを維持する限り、私はそれをお勧めします。しかし、いったんプロになったら、独自のフレームワークを作り直すのではなく、既存のフレームワークを選択することを強くお勧めします。既存の十分に開発されたフレームワークを学ぶことは、堅牢なフレームワークを自分で開発して維持するよりも、より短い時間で済みます。

以下の詳細な説明では、実装が簡単なので、リクエストベースのMVCに制限します。


フロントコントローラーパターンメディエーターパターン

最初に、コントローラーパーツはフロントコントローラーパターン(特殊な種類のメディエーターパターン)を実装する必要があります。これは、すべての要求の中央エントリーポイントを提供する単一のサーブレットのみで構成する必要があります。pathinfoやservletpath、メソッド、特定のパラメーターなど、リクエストによって利用可能な情報に基づいてモデルを作成する必要があります。ビジネスモデルが呼ばれるAction以下にHttpServlet例。

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try {
        Action action = ActionFactory.getAction(request);
        String view = action.execute(request, response);

        if (view.equals(request.getPathInfo().substring(1)) {
            request.getRequestDispatcher("/WEB-INF/" + view + ".jsp").forward(request, response);
        }
        else {
            response.sendRedirect(view); // We'd like to fire redirect in case of a view change as result of the action (PRG pattern).
        }
    }
    catch (Exception e) {
        throw new ServletException("Executing action failed.", e);
    }
}

アクションを実行すると、ビューを見つけるための識別子が返されます。最も簡単な方法は、JSPのファイル名として使用することです。具体的には、このサーブレットの地図url-patternではweb.xml、例えば/pages/**.doあるいは単に*.html

たとえば、プレフィックスパターンの場合、http://example.com/pages/registerなどの/pages/*URLを呼び出すことができます http://example.com/pages/login、などと提供し/WEB-INF/register.jsp/WEB-INF/login.jsp適切なGETとPOSTアクションで。部品registerloginなどはrequest.getPathInfo()、上記の例のようにして使用できます。

あなたが使用している場合は、サフィックス・パターンが好き*.do*.htmlなど、あなたでしその後、URLのinvokeのようにhttp://example.com/register.do http://example.com/login.do、など、あなたが変更する必要がありますこの回答のコード例(もActionFactory)を抽出するregisterloginによって部品をrequest.getServletPath()代わりに。


戦略パターン

Action従うべきであるStrategyパターンを。これは、抽象メソッドの渡された引数に基づいて作業を行う抽象/インターフェース型として定義する必要があります(これは、コマンドパターン抽象/インターフェースタイプは、実装の作成中に渡される引数)。

public interface Action {
    public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

Exceptionようなカスタム例外を使用して、より具体的にすることができActionExceptionます。これは基本的なキックオフの例に過ぎず、残りはすべてあなた次第です。

LoginActionこれは(名前が示すように)ユーザーにログインするの例です。User自身のターンであるデータモデルビューはの存在を認識していますUser

public class LoginAction implements Action {

    public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        User user = userDAO.find(username, password);

        if (user != null) {
            request.getSession().setAttribute("user", user); // Login user.
            return "home"; // Redirect to home page.
        }
        else {
            request.setAttribute("error", "Unknown username/password. Please retry."); // Store error message in request scope.
            return "login"; // Go back to redisplay login form with error.
        }
    }

}

ファクトリーメソッドパターン

ActionFactory従うべきFactory Methodパターンを。基本的に、抽象/インターフェース型の具体的な実装を返す作成メソッドを提供する必要があります。この場合、Actionリクエストによって提供された情報に基づいて、インターフェースの実装を返す必要があります。たとえば、メソッドpathinfo(pathinfoは、クエリ文字列を除く、リクエストURLのコンテキストとサーブレットパスの後の部分です)。

public static Action getAction(HttpServletRequest request) {
    return actions.get(request.getMethod() + request.getPathInfo());
}

actions順番にいくつかの静的/アプリケーションにわたるでなければなりませんMap<String, Action>すべての既知のアクションを保持する全体である。この地図を埋める方法はあなた次第です。ハードコーディング:

actions.put("POST/register", new RegisterAction());
actions.put("POST/login", new LoginAction());
actions.put("GET/logout", new LogoutAction());
// ...

または、クラスパスのプロパティ/ XML構成ファイルに基づいて構成可能:(疑似)

for (Entry entry : configuration) {
    actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
}

または、特定のインターフェースやアノテーションを実装するクラスのクラスパスのスキャンに基づいて動的に:(疑似)

for (ClassFile classFile : classpath) {
    if (classFile.isInstanceOf(Action.class)) {
       actions.put(classFile.getAnnotation("mapping"), classFile.newInstance());
    }
}

Actionマッピングがない場合のために「何もしない」を作成することを覚えておいてください。たとえば、request.getPathInfo().substring(1)そのときに直接返すようにします。


その他のパタ​​ーン

これらはこれまでのところ重要なパターンでした。

さらに一歩進んだ方法として、Facadeパターンを使用してContextクラスを作成し、クラスを作成して要求オブジェクトと応答オブジェクトをラップし、要求オブジェクトと応答オブジェクトに委譲し、Action#execute()代わりにメソッドに引数として渡すいくつかの便利なメソッドを提供できます。これにより、抽象サーブレット層が追加され、未加工のサーブレットAPIが非表示になります。その後、基本的にすべての実装で宣言がゼロになる はずです。JSF用語では、これはおよびクラスが行っていることです。あなたはこの答えで具体的な例を見つけることができますimport javax.servlet.*ActionFacesContextExternalContext

次に、リクエストパラメータの収集、変換、検証、モデル値の更新、アクションの実行などのタスクを分割するために、追加の抽象化レイヤーを追加する場合の状態パターンがあります。JSF用語では、これはLifeCycleが行っていることです。

次に、モデルにアタッチできるコンポーネントベースのビューを作成する場合の複合パターンがあり、その動作はリクエストベースのライフサイクルの状態に依存します。JSF用語では、これはUIComponent代表的なものです。

このようにして、コンポーネントベースのフレームワークに向かって少しずつ進化させることができます。


以下も参照してください。


4
@masato:たとえば静的初期化ブロックでこれを行うことができます。
BalusC 2010

1
@masato:ちなみに、それらをから取得したい場合web.xmlServletContextListener、これにを使用できます。工場にそれを実装させ(そしてのように登録し<listener>web.xml)、contextInitialized()メソッド中に充填作業を行います。
BalusC 2010

3
代わりに、「post_servlet」がアクションで実行する必要のあるジョブを実行してください。複数のサーブレットを使用しないでください。ビジネスはアクションクラスで行う必要があります。新しいリクエストにしたい場合は、別のビューに戻ってリダイレクトを引き起こし、GETリクエストに関連付けられた新しいアクションでジョブを実行します。
BalusC 2010

2
依存します。最も簡単なのはAction、通常のサーブレットと同じように、実装で正しく実行することです(基本的な例については、サーブレットのwikiも参照してください。これは、Validatorインターフェイスにさらにリファクタリングできます)。ただし、アクションを呼び出す前にそれを行うこともできますが、ビューごとに検証ルールを知っておく必要があるため、これはより複雑になります。JSFはこれをXHTMLマークアップrequired="true"validator="customValidatorName"、などを提供することでカバーしています。
BalusC 2012年

2
@AndreyBotalov:JSF、Spring MVC、Wicket、Struts2などのMVCフレームワークのソースコードを確認してください。これらはすべてオープンソースです。
BalusC 2012

13

打ちのめされたMVCパターンでは、サーブレットは「C」-コントローラーです。

その主な仕事は、初期リクエストの評価を行い、初期評価に基づく処理を特定のワーカーにディスパッチすることです。ワーカーの責任の1つは、いくつかのプレゼンテーションレイヤーBeanをセットアップし、リクエストをJSPページに転送してHTMLをレンダリングすることです。したがって、この理由だけで、リクエストオブジェクトをサービス層に渡す必要があります。

しかし、私は生のServletクラスを書き始めませんでした。彼らが行う作業は非常に予測可能で定型的であり、フレームワークは非常にうまく機能しています。さいわい、Apache WicketJava Server FacesSpringなど、時間をかけてテストされた多数の候補があります(アルファベット順)。


5

私見、責任分担の観点から見れば、Webアプリケーションの場合とそれほど違いはありません。ただし、レイヤーは明確にしてください。Webコントロールに固有のコントロールやコードなど、プレゼンテーション目的で純粋に何でもプレゼンテーションレイヤーに保持します。エンティティをビジネスレイヤーに保持し、すべての機能(追加、編集、削除など)などをビジネスレイヤーに保持するだけです。ただし、プレゼンテーション層で処理されるようにブラウザにレンダリングします。.Netの場合、ASP.NET MVCパターンは、レイヤーを分離しておくという点で非常に優れています。MVCパターンを調べます。


サーブレットに何を入れるべきかを少し明示できますか?
mawia 2010

MVCを使用する場合は、サーブレットがコントローラーになるはずです。
2010

3

私はStrutsフレームワークを使用しましたが、学ぶのはかなり簡単です。Strutsフレームワークを使用すると、サイトの各ページに次のアイテムが含まれます。

1)HTMLページが更新されるたびに、使用されるアクションが呼び出されます。アクションは、ページが最初に読み込まれたときにフォームにデータを入力し、Web UIとビジネスレイヤーの間の対話を処理する必要があります。jspページを使用して変更可能なJavaオブジェクトを変更する場合は、ユーザーがページを保存しない限り元のデータが変更されないように、Javaオブジェクトのコピーを元のフォームではなくフォームに保存する必要があります。

2)アクションとjspページの間でデータを転送するために使用されるフォーム。このオブジェクトは、jspファイルにアクセスできるようにする必要がある属性のゲッターとセッターのセットで構成する必要があります。フォームには、永続化される前にデータを検証するメソッドもあります。

3)ページの最終的なHTMLをレンダリングするために使用されるjspページ。jspページは、フォーム内のデータにアクセスして操作するために使用されるHTMLと特別なstrutsタグのハイブリッドです。Strutsを使用すると、Javaコードをjspファイルに挿入できますが、コードを読みにくくするため、慎重に行う必要があります。jspファイル内のJavaコードはデバッグが難しく、単体テストはできません。jspファイル内に4〜5行を超えるJavaコードを記述している場合は、コードをアクションに移動する必要があります。


注:Struts 2では、フォームオブジェクトは代わりにモデルと呼ばれますが、元の回答で説明したのと同じように機能します。
EsotericNonsense

3

BalusCの優れた回答は、Webアプリケーションのパターンのほとんどをカバーしています。

一部のアプリケーションではChain-of-responsibility_patternが必要になる場合があります

オブジェクト指向設計では、責任の連鎖パターンは、コマンドオブジェクトのソースと一連の処理オブジェクトで構成される設計パターンです。各処理オブジェクトには、処理可能なコマンドオブジェクトのタイプを定義するロジックが含まれています。残りは、チェーン内の次の処理オブジェクトに渡されます。

このパターンを使用するユースケース:

リクエスト(コマンド)を処理するハンドラが不明で、このリクエストを複数のオブジェクトに送信できる場合。通常は、後継者をオブジェクトに設定します。現在のオブジェクトがリクエストを処理できないか、リクエストを部分的に処理できず、同じリクエストを後続のオブジェクトに転送できる場合。

役立つSEの質問/記事:

なぜデコレータよりも責任の連鎖を使用するのですか?

責任の連鎖の一般的な使用法?

oodesignの責任の連鎖パターン

ソース作成からのchain_of_responsibility

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.