Webbasierte Anwendungen für Entwurfsmuster [geschlossen]

359

Ich entwerfe eine einfache webbasierte Anwendung. Ich bin neu in dieser webbasierten Domain. Ich brauchte Ihren Rat bezüglich der Entwurfsmuster wie der Verteilung der Verantwortung auf Servlets, der Kriterien für die Erstellung eines neuen Servlets usw.

Tatsächlich habe ich nur wenige Entitäten auf meiner Homepage und entsprechend jeder von ihnen haben wir einige Optionen wie Hinzufügen, Bearbeiten und Löschen. Früher habe ich ein Servlet pro Option verwendet, z. B. Servlet1 zum Hinzufügen von Entity1, Servlet2 zum Bearbeiten von Entity1 usw. Auf diese Weise hatten wir eine große Anzahl von Servlets.

Jetzt ändern wir unser Design. Meine Frage ist, wie Sie genau auswählen, wie Sie die Verantwortung für ein Servlet auswählen. Sollten wir ein Servlet pro Entität haben, das alle Optionen verarbeitet und die Anforderung an die Serviceschicht weiterleitet. Oder sollten wir ein Servlet für die gesamte Seite haben, das die gesamte Seitenanforderung verarbeitet und dann an die entsprechende Serviceschicht weiterleitet? Sollte das Anforderungsobjekt an die Serviceschicht weitergeleitet werden oder nicht?

Mawia
quelle
8
Nicht wirklich offizielle Designmuster, aber vergessen Sie nicht PRG (post-redirect-get) und Hijax (machen Sie Arbeit ohne js zuerst, dann entführen Sie die Links und Schaltflächen mit Ajax)
Neil McGuigan

Antworten:

488

Eine etwas anständige Webanwendung besteht aus einer Mischung von Designmustern. Ich werde nur die wichtigsten erwähnen.


Model View Controller-Muster

Das zentrale (architektonische) Entwurfsmuster, das Sie verwenden möchten, ist das Model-View-Controller-Muster . Der Controller soll durch ein Servlet dargestellt werden, das (in) direkt ein bestimmtes Modell und eine bestimmte Ansicht basierend auf der Anforderung erstellt / verwendet . Das Modell soll durch javabäische Klassen dargestellt werden. Dies ist im Geschäftsmodell, das die Aktionen (Verhalten) enthält, und im Datenmodell, das die Daten (Informationen) enthält, häufig weiter teilbar . Die Ansicht soll durch JSP-Dateien dargestellt werden, die über EL (Expression Language) direkten Zugriff auf das ( Daten- ) Modell haben .

Dann gibt es Variationen, die darauf basieren, wie Aktionen und Ereignisse behandelt werden. Die beliebtesten sind:

  • Anforderungs- (Aktions-) MVC : Dies ist am einfachsten zu implementieren. Das ( Geschäfts- ) Modell arbeitet direkt mit HttpServletRequestund HttpServletResponseObjekten. Sie müssen die Anforderungsparameter (meistens) selbst erfassen, konvertieren und validieren. Die Ansicht kann durch einfaches Vanille-HTML / CSS / JS dargestellt werden und behält den Status nicht über Anforderungen hinweg bei. So funktioniert unter anderem Spring MVC , Struts and Stripes .

  • Komponentenbasierte MVC : Dies ist schwieriger zu implementieren. Am Ende erhalten Sie jedoch ein einfacheres Modell und eine einfachere Ansicht, in der die gesamte "rohe" Servlet-API vollständig entfernt wird. Sie sollten die Anforderungsparameter nicht selbst erfassen, konvertieren und validieren müssen. Der Controller führt diese Aufgabe aus und legt die gesammelten, konvertierten und validierten Anforderungsparameter im Modell fest . Sie müssen lediglich Aktionsmethoden definieren, die direkt mit den Modelleigenschaften funktionieren. Die Ansicht wird durch "Komponenten" in der Variante von JSP-Taglibs oder XML-Elementen dargestellt, die wiederum HTML / CSS / JS generieren. Der Status der Ansichtfür die nachfolgenden Anfragen wird in der Sitzung gepflegt. Dies ist besonders hilfreich für serverseitige Konvertierungs-, Validierungs- und Wertänderungsereignisse. So geht unter anderem JSF , Wicket and Play! funktioniert.

Nebenbei bemerkt, das Hobby mit einem selbstgebauten MVC-Framework ist eine sehr schöne Lernübung, und ich empfehle es, solange Sie es für persönliche / private Zwecke aufbewahren. Wenn Sie jedoch erst einmal professionell sind, wird dringend empfohlen, ein vorhandenes Framework auszuwählen, anstatt Ihr eigenes neu zu erfinden. Das Erlernen eines vorhandenen und gut entwickelten Frameworks dauert langfristig weniger lange als das Entwickeln und Verwalten eines robusten Frameworks.

In der folgenden detaillierten Erklärung beschränke ich mich auf anforderungsbasierte MVC, da dies einfacher zu implementieren ist.


Front Controller-Muster ( Mediator-Muster )

Zunächst sollte der Controller- Teil das Front-Controller-Muster implementieren (eine spezielle Art von Mediator-Muster ). Es sollte nur aus einem einzigen Servlet bestehen, das einen zentralen Einstiegspunkt für alle Anforderungen bietet. Das Modell sollte auf der Grundlage der von der Anforderung verfügbaren Informationen erstellt werden, z. B. der Pfadinfo oder des Servletpfads, der Methode und / oder bestimmter Parameter. Das Geschäftsmodell wird Actionim folgenden HttpServletBeispiel aufgerufen .

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);
    }
}

Das Ausführen der Aktion sollte eine Kennung zurückgeben, um die Ansicht zu lokalisieren. Am einfachsten wäre es, es als Dateinamen der JSP zu verwenden. Karte Servlet auf einem bestimmten url-patternin web.xml, zum Beispiel /pages/*, *.dooder auch nur *.html.

Im Fall von Präfix-Muster , wie zum Beispiel /pages/*könnten Sie dann invoke URL ist wie http://example.com/pages/register , http://example.com/pages/login , etc. und bietet /WEB-INF/register.jsp, /WEB-INF/login.jspmit der entsprechenden GET und POST - Aktionen . Die Teile register, loginsind etc dann zur Verfügung , request.getPathInfo()wie in obigem Beispiel.

Wenn Sie mit Suffix-Muster wie *.do, *.htmletc, dann könnte man dann invoke URL wie http://example.com/register.do , http://example.com/login.do , etc , und Sie sollten das ändern Codebeispiele in dieser Antwort (auch die ActionFactory), um stattdessen die registerund loginTeile zu extrahieren request.getServletPath().


Strategiemuster

Die Actionsollten dem Strategiemuster folgen . Es muss als abstrakter / Schnittstellentyp definiert werden, der die Arbeit basierend auf den übergebenen Argumenten der abstrakten Methode ausführen soll (dies ist der Unterschied zum Befehlsmuster , wobei der abstrakte / Schnittstellentyp die Arbeit basierend auf dem ausführen soll Argumente, die bei der Erstellung der Implementierung übergeben wurden).

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

Möglicherweise möchten Sie die Exceptionmit einer benutzerdefinierten Ausnahme wie präzisieren ActionException. Es ist nur ein einfaches Kickoff-Beispiel, der Rest liegt ganz bei Ihnen.

Hier ist ein Beispiel für ein, LoginActiondas (wie der Name schon sagt) den Benutzer anmeldet. Das Userselbst ist wiederum ein Datenmodell . Die Ansicht ist sich der Anwesenheit der bewusst 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.
        }
    }

}

Fabrikmethodenmuster

Das ActionFactorysollte dem Factory-Methodenmuster folgen . Grundsätzlich sollte eine Erstellungsmethode bereitgestellt werden, die eine konkrete Implementierung eines Abstract- / Interface-Typs zurückgibt. In diesem Fall sollte eine Implementierung der ActionSchnittstelle basierend auf den von der Anforderung bereitgestellten Informationen zurückgegeben werden. Beispiel: Methode und Pfadinfo (die Pfadinfo ist der Teil nach dem Kontext und dem Servlet-Pfad in der Anforderungs-URL, ausgenommen die Abfragezeichenfolge).

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

Die actionswiederum sollte statisch / anwendungsweit sein Map<String, Action>und alle bekannten Aktionen enthalten. Es liegt an Ihnen, wie Sie diese Karte füllen. Hardcodierung:

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

Oder konfigurierbar basierend auf einer Eigenschaften- / XML-Konfigurationsdatei im Klassenpfad: (Pseudo)

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

Oder dynamisch basierend auf einem Scan im Klassenpfad nach Klassen, die eine bestimmte Schnittstelle und / oder Anmerkung implementieren: (Pseudo)

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

Denken Sie daran, ein "Nichts tun" Actionfür den Fall zu erstellen, dass keine Zuordnung vorliegt. Lassen Sie es zum Beispiel direkt das request.getPathInfo().substring(1)Dann zurückgeben.


Andere Muster

Das waren bisher die wichtigen Muster.

Um noch einen Schritt weiter zu kommen, können Sie das Facade-Muster verwenden , um eine ContextKlasse zu erstellen , die wiederum die Anforderungs- und Antwortobjekte umschließt und verschiedene praktische Methoden bietet, die an die Anforderungs- und Antwortobjekte delegieren und diese Action#execute()stattdessen als Argument an die Methode übergeben. Dadurch wird eine zusätzliche abstrakte Ebene hinzugefügt, um die unformatierte Servlet-API auszublenden. Sie sollten dann grundsätzlich in jeder Implementierung keine import javax.servlet.* Deklarationen haben Action. In JSF-Begriffen ist dies das, was die Klassen FacesContextund ExternalContexttun. In dieser Antwort finden Sie ein konkretes Beispiel .

Dann gibt es das Statusmuster für den Fall, dass Sie eine zusätzliche Abstraktionsschicht hinzufügen möchten, um die Aufgaben des Sammelns der Anforderungsparameter, Konvertieren, Validieren, Aktualisieren der Modellwerte und Ausführen der Aktionen aufzuteilen. In JSF-Begriffen ist dies das, was der LifeCycletut.

Dann gibt es das zusammengesetzte Muster für den Fall, dass Sie eine komponentenbasierte Ansicht erstellen möchten, die an das Modell angehängt werden kann und deren Verhalten vom Status des anforderungsbasierten Lebenszyklus abhängt. In JSF-Begriffen ist dies das, was die UIComponentdarstellen.

Auf diese Weise können Sie sich Stück für Stück zu einem komponentenbasierten Framework entwickeln.


Siehe auch:

BalusC
quelle
4
@masato: Sie können dies beispielsweise in einem statischen Initialisierungsblock tun.
BalusC
1
@masato: Übrigens, wenn Sie sie abrufen möchten web.xml, können Sie ein ServletContextListenerdafür verwenden. Lassen Sie es vom Werk implementieren (und registrieren Sie sich wie <listener>in web.xml) und erledigen Sie den Füllvorgang während des contextInitialized()Verfahrens.
BalusC
3
Führen Sie stattdessen die Aufgabe aus, die das "post_servlet" in der Aktion ausführen soll. Sie sollten nicht mehr als ein Servlet haben. Geschäftsangelegenheiten sollten in Aktionsklassen erledigt werden. Wenn Sie möchten, dass es sich um eine neue Anforderung handelt, kehren Sie zu einer anderen Ansicht zurück, die eine Umleitung verursachen würde, und führen Sie die Aufgabe in der neuen Aktion aus, die der GET-Anforderung zugeordnet ist.
BalusC
2
Hängt davon ab. Am einfachsten ist es, es in der ActionImplementierung genauso wie bei normalen Servlets richtig zu machen (siehe auch Servlet-Wiki für ein grundlegendes Beispiel, das Sie weiter in eine ValidatorSchnittstelle umgestalten können). Sie können dies jedoch auch vor dem Aufrufen der Aktion tun. Dies ist jedoch komplexer, da die Validierungsregeln pro Ansicht bekannt sein müssen. JSF ist dies durch das Angebot gedeckt required="true", validator="customValidatorName"usw. in dem XHTML - Markup.
BalusC
2
@AndreyBotalov: Überprüfen Sie den Quellcode von MVC-Frameworks wie JSF, Spring MVC, Wicket, Struts2 usw. Sie sind alle Open Source.
BalusC
13

In dem verprügelten MVC-Muster ist das Servlet "C" - Controller.

Seine Hauptaufgabe besteht darin, eine erste Anforderungsbewertung durchzuführen und dann die Verarbeitung basierend auf der anfänglichen Bewertung an den spezifischen Mitarbeiter zu senden. Eine der Aufgaben des Arbeiters kann darin bestehen, einige Präsentations-Layer-Beans einzurichten und die Anforderung zum Rendern von HTML an die JSP-Seite weiterzuleiten. Allein aus diesem Grund müssen Sie das Anforderungsobjekt an die Serviceschicht übergeben.

Ich würde jedoch nicht anfangen, rohe ServletKlassen zu schreiben . Die Arbeit, die sie leisten, ist sehr vorhersehbar und auf dem Kessel, was das Framework sehr gut macht. Glücklicherweise gibt es viele verfügbare, bewährte Kandidaten (in alphabetischer Reihenfolge): Apache Wicket , Java Server Faces , Spring, um nur einige zu nennen.

Alexander Pogrebnyak
quelle
5

Meiner Meinung nach gibt es bei Webanwendungen keinen großen Unterschied, wenn Sie sie unter dem Gesichtspunkt der Verantwortungszuweisung betrachten. Behalten Sie jedoch die Klarheit in der Ebene. Bewahren Sie alles, was nur für Präsentationszwecke bestimmt ist, in der Präsentationsebene auf, z. B. das Steuerelement und den Code, die für die Websteuerelemente spezifisch sind. Behalten Sie einfach Ihre Entitäten in der Geschäftsschicht und alle Funktionen (wie Hinzufügen, Bearbeiten, Löschen) usw. in der Geschäftsschicht. Sie werden jedoch in den Browser gerendert, um in der Präsentationsebene verarbeitet zu werden. Für .Net ist das ASP.NET MVC-Muster sehr gut, um die Ebenen getrennt zu halten. Schauen Sie sich das MVC-Muster an.

Kangkan
quelle
Können Sie etwas explizit angeben, was im Servlet enthalten sein soll?
Mawia
Das Servlet sollte der Controller sein, wenn Sie MVC verwenden.
Kangkan
3

Ich habe das Federbein- Framework verwendet und finde es ziemlich einfach zu lernen. Bei Verwendung des Struts-Frameworks enthält jede Seite Ihrer Site die folgenden Elemente.

1) Eine Aktion, die verwendet wird, wird jedes Mal aufgerufen, wenn die HTML-Seite aktualisiert wird. Die Aktion sollte die Daten im Formular beim ersten Laden der Seite füllen und Interaktionen zwischen der Web-Benutzeroberfläche und der Geschäftsschicht verarbeiten. Wenn Sie die JSP-Seite zum Ändern eines veränderlichen Java-Objekts verwenden, sollte eine Kopie des Java-Objekts im Formular und nicht im Original gespeichert werden, damit die Originaldaten nur geändert werden, wenn der Benutzer die Seite speichert.

2) Das Formular, mit dem Daten zwischen der Aktion und der JSP-Seite übertragen werden. Dieses Objekt sollte aus einer Reihe von Gettern und Setzern für Attribute bestehen, auf die die JSP-Datei zugreifen muss. Das Formular verfügt auch über eine Methode zum Überprüfen von Daten, bevor diese beibehalten werden.

3) Eine JSP-Seite, die zum Rendern des endgültigen HTML-Codes der Seite verwendet wird. Die JSP-Seite ist eine Mischung aus HTML- und speziellen Struts-Tags, mit denen auf Daten im Formular zugegriffen und diese bearbeitet werden können. Obwohl Struts es Benutzern ermöglicht, Java-Code in JSP-Dateien einzufügen, sollten Sie diesbezüglich sehr vorsichtig sein, da dies das Lesen Ihres Codes erschwert. Java-Code in JSP-Dateien ist schwer zu debuggen und kann nicht Unit-getestet werden. Wenn Sie feststellen, dass Sie mehr als 4-5 Zeilen Java-Code in eine JSP-Datei schreiben, sollte der Code wahrscheinlich in die Aktion verschoben werden.

EsotericNonsense
quelle
Hinweis: In Streben 2 wird das Formularobjekt stattdessen als Modell bezeichnet, funktioniert jedoch genauso wie in meiner ursprünglichen Antwort beschrieben.
EsotericNonsense
3

Die ausgezeichnete Antwort von BalusC deckt die meisten Muster für Webanwendungen ab.

Für einige Anwendungen ist möglicherweise Chain-of-Responsibility- Muster erforderlich

Beim objektorientierten Entwurf ist das Verantwortungskettenmuster ein Entwurfsmuster, das aus einer Quelle von Befehlsobjekten und einer Reihe von Verarbeitungsobjekten besteht. Jedes Verarbeitungsobjekt enthält eine Logik, die die Arten von Befehlsobjekten definiert, mit denen es umgehen kann. Der Rest wird an das nächste Verarbeitungsobjekt in der Kette übergeben.

Anwendungsfall, um dieses Muster zu verwenden:

Wenn der Handler zum Verarbeiten einer Anforderung (Befehl) unbekannt ist und diese Anforderung an mehrere Objekte gesendet werden kann. Im Allgemeinen setzen Sie den Nachfolger auf Objekt. Wenn das aktuelle Objekt die Anforderung nicht verarbeiten oder die Anforderung teilweise verarbeiten kann, leiten Sie dieselbe Anforderung an das Nachfolgeobjekt weiter .

Nützliche SE-Fragen / Artikel:

Warum sollte ich jemals eine Verantwortungskette gegenüber einem Dekorateur verwenden?

Gemeinsame Verwendungen für die Verantwortungskette?

Chain-of-Responsibility-Muster von Oodesign

Verantwortungskette aus der Quellenherstellung

Ravindra Babu
quelle