web-dev-qa-db-ja.com

デザインパターンのWebベースのアプリケーション

単純なWebベースのアプリケーションを設計しています。このWebベースのドメインは初めてで、サーブレット間で責任をどのように分散させるか、新しいサーブレットを作成する基準など、設計パターンに関するアドバイスが必要でした。

実際、ホームページにはエンティティがほとんどなく、各エンティティに対応して、追加、編集、削除などのオプションはほとんどありません。以前は、entity1の追加にServlet1、entity1の編集にServlet2などのオプションごとに1つのサーブレットを使用していましたが、このようにして多数のサーブレットができました。

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

354
mawia

少しまともなWebアプリケーションは、デザインパターンの組み合わせで構成されています。最も重要なものだけに言及します。


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

使用したいコア(アーキテクチャ)デザインパターンは Model-View-Controller pattern です。 Controllerは、特定のModelおよび要求に基づいて表示ModelはJavabeanクラスで表されます。これは、アクション(動作)を含むビジネスモデルおよびデータ(情報)を含むデータモデルでさらに分割可能です。 Viewは、(DataModelに直接アクセスできるJSPファイルで表されますEL(Expression Language)による。

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

  • リクエスト(アクション)ベースのMVC:これは実装が最も簡単です。 (BusinessModelは、HttpServletRequestおよびHttpServletResponseオブジェクトと直接連携します。 (ほとんど)自分で要求パラメーターを収集、変換、検証する必要があります。 Viewは、単純なVanilla HTML/CSS/JSで表すことができ、リクエスト間で状態を維持しません。 Spring MVCStruts および Stripes の動作はこれです。

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

サイドノートとして、自家製のMVCフレームワークでの趣味は非常に良い学習の練習であり、個人的/私的な目的のためにそれを保持する限り、それをお勧めします。ただし、プロになったら、独自のフレームワークを再発明するのではなく、既存のフレームワークを選択することを強くお勧めします。既存の十分に開発されたフレームワークを学習することは、堅牢なフレームワークを自分で開発および維持するよりも、長期的に短い時間で済みます。

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


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

最初に、Controller部分は Front Controller pattern (特殊な種類の Mediator pattern )を実装する必要があります。これは、すべてのリクエストの集中エントリポイントを提供する単一のサーブレットのみで構成する必要があります。 pathinfoまたはservletpath、メソッド、特定のパラメータなど、リクエストで利用可能な情報に基づいてModelを作成する必要があります。 ビジネスモデルは、以下の例では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

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

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


戦略パターン

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

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

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

以下は、LoginActionの例です(名前が示すとおり)ユーザーをログインします。 User自体はData Modelです。 ViewUserの存在を認識しています。

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

}

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

ActionFactoryFactory method pattern に従う必要があります。基本的に、抽象型/インターフェイス型の具体的な実装を返す作成メソッドを提供する必要があります。この場合、リクエストによって提供された情報に基づいてActionインターフェイスの実装を返す必要があります。たとえば、 method および pathinfo (pathinfoは、クエリURLを除く、リクエスト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 pattern を使用してContextクラスを作成します。このクラスは、要求オブジェクトと応答オブジェクトをラップし、要求オブジェクトと応答オブジェクトに委任するいくつかの便利なメソッドを提供し、引数として渡します代わりにAction#execute()メソッドに。これにより、追加の抽象レイヤーが追加され、生のサーブレットAPIが隠されます。その後、基本的にすべてのAction実装でzeroimport javax.servlet.*宣言で終わる必要があります。 JSFの用語では、これが FacesContext および ExternalContext クラスが行っていることです。具体例は this answer にあります。

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

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

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


こちらもご覧ください:

482
BalusC

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

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

ただし、生のServletクラスの作成は開始しません。彼らが行う作業は非常に予測可能で定型的であり、フレームワークは非常にうまく機能します。幸いなことに、多くの利用可能なタイムテスト済みの候補があります(アルファベット順): Apache WicketJava Server FacesSpring 少ない。

12

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

5
Kangkan

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

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

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

このパターンを使用する場合:

要求(コマンド)を処理するハンドラーが不明であり、この要求を複数のオブジェクトに送信できる場合。通常、successorをオブジェクトに設定します。現在のオブジェクトがリクエストを処理できない場合、またはリクエストを部分的に処理して、同じリクエストをsuccessorオブジェクトに転送する場合。

便利なSEの質問/記事:

デコレータよりも責任の連鎖を使用する理由は?

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

chain-of-responsibility-pattern oodesignから

chain_of_responsibility sourcemakingから

3
Ravindra babu

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コードを記述している場合は、コードをアクションに移動する必要があります。

3