Aplikacje internetowe Design Patterns [zamknięte]

359

Projektuję prostą aplikację internetową. Jestem nowy w tej domenie internetowej. Potrzebowałem twojej porady dotyczącej wzorców projektowych, takich jak sposób podziału odpowiedzialności między serwletami, kryteria tworzenia nowego serwletu itp.

W rzeczywistości mam kilka podmiotów na mojej stronie głównej i odpowiadających każdemu z nich mamy kilka opcji, takich jak dodawanie, edycja i usuwanie. Wcześniej korzystałem z jednego serwletu na opcje, takie jak Servlet1 do dodawania encji1, Servlet2 do edycji encji1 itd., W ten sposób otrzymaliśmy dużą liczbę serwletów.

Teraz zmieniamy nasz projekt. Moje pytanie brzmi: jak dokładnie wybierasz sposób, w jaki wybierasz odpowiedzialność serwletu. Czy powinniśmy mieć jeden serwlet na jednostkę, który przetworzy wszystkie jego opcje i przekaże żądanie do warstwy usług. Czy też powinniśmy mieć jeden serwlet dla całej strony, który przetworzy żądanie całej strony, a następnie prześle go do odpowiedniej warstwy usługi? Ponadto, jeśli obiekt żądania zostanie przekazany do warstwy usługi, czy nie.

mawia
źródło
8
Niezupełnie oficjalne wzorce projektowe, ale nie zapomnij PRG (post-redirect-GET) i Hijax (marka pracę bez js, potem porwać linki i przyciski z Ajax)
Neil McGuigan

Odpowiedzi:

488

Trochę przyzwoita aplikacja internetowa składa się z mieszanki wzorców projektowych. Wymienię tylko te najważniejsze.


Widok modelu Model kontrolera

Podstawowy (architektoniczny) wzorzec projektowy, którego chcesz użyć, to wzorzec Model-View-Controller . Kontroler ma być reprezentowana przez aplet, który (w) bezpośrednio tworzy / używa konkretny model i widok na podstawie wniosku. Model ma być reprezentowane przez klasy JavaBeans. Jest to często podzielne w modelu biznesowym, który zawiera działania (zachowanie) i modelu danych, który zawiera dane (informacje). View ma być reprezentowane przez pliki JSP, które mają bezpośredni dostęp do ( danych ) model EL (Expression Language).

Następnie istnieją odmiany zależne od tego, jak obsługiwane są akcje i zdarzenia. Popularne to:

  • MVC oparty na zapytaniach (działaniach) : jest to najprostszy do wdrożenia. The ( Biznes ) model współpracuje bezpośrednio z HttpServletRequesti HttpServletResponseobiekty. Musisz samodzielnie zebrać, przekonwertować i zweryfikować parametry żądania. View może być reprezentowany przez plain vanilla HTML / CSS / JS i nie utrzymania stanu całej żądań. Tak działa między innymi Spring MVC , Struts and Stripes .

  • Komponenty MVC : trudniejsze do wdrożenia. Ale kończy się to prostszym modelem i widokiem, w którym wszystkie „surowe” API serwletów są całkowicie oderwane. Nie powinno być potrzeby samodzielnego gromadzenia, konwertowania i sprawdzania parametrów żądania. Controller robi to zadanie i ustawia zebrane, przeliczone i potwierdzone parametry żądania w Modelu . Wystarczy zdefiniować metody działania, które będą działać bezpośrednio z właściwościami modelu. View jest reprezentowany przez „składniki” w smaku taglibs JSP lub elementów XML, który z kolei generuje HTML / CSS / JS. Stan widokudla kolejnych żądań jest utrzymywany w sesji. Jest to szczególnie przydatne w przypadku konwersji, sprawdzania poprawności i zmian wartości po stronie serwera. Tak między innymi JSF , Wicket i Play! Pracuje.

Na marginesie, hobby ze stworzonym przez siebie frameworkiem MVC jest bardzo przyjemnym ćwiczeniem edukacyjnym i polecam je, dopóki przechowujesz je do celów osobistych / prywatnych. Ale kiedy przejdziesz do profesjonalizmu, zdecydowanie zaleca się wybranie istniejącego frameworka, zamiast odkrywania go na nowo. Nauka istniejących i dobrze rozwiniętych ram zajmuje o wiele mniej czasu niż samodzielne opracowanie i utrzymanie solidnych ram.

W poniższym szczegółowym wyjaśnieniu ograniczę się do MVC opartego na żądaniach, ponieważ jest to łatwiejsze do wdrożenia.


Wzorzec kontrolera przedniego ( wzór Mediatora )

Najpierw część kontrolera powinna implementować wzór kontrolera frontowego (który jest specjalistycznym rodzajem wzoru mediatora ). Powinien składać się tylko z jednego serwletu, który zapewnia scentralizowany punkt wejścia dla wszystkich żądań. Powinien utworzyć model na podstawie informacji dostępnych w żądaniu, takich jak pathinfo lub ścieżka serwletu, metoda i / lub określone parametry. Model biznesowy jest nazywany Actionw poniższym HttpServletprzykładzie.

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

Wykonanie akcji powinno zwrócić jakiś identyfikator, aby zlokalizować widok. Najprościej byłoby użyć go jako nazwy pliku JSP. Map tego apletu na określonym url-patternw web.xml, na przykład /pages/*, *.doalbo nawet po prostu *.html.

W przypadku prefix-wzory jak na przykład /pages/*można było powołać się URL jak http://example.com/pages/register , http://example.com/pages/login , etc i zapewnienia /WEB-INF/register.jsp, /WEB-INF/login.jspz odpowiednim GET i POST działań . Części register, loginitp Następnie dostępnych request.getPathInfo(), jak w powyższym przykładzie.

Kiedy używasz sufiks wzorców jak *.do, *.htmlitp, to można powołać się URL jak http://example.com/register.do , http://example.com/login.do itp i trzeba zmienić przykłady kodu w tej odpowiedzi (także ActionFactory), aby zamiast tego wyodrębnić części registeri .loginrequest.getServletPath()


Wzór strategii

ActionPowinny być zgodne z wzorca Strategy . Należy go zdefiniować jako typ abstrakcyjny / interfejs, który powinien wykonywać pracę na podstawie przekazanych argumentów metody abstrakcyjnej (jest to różnica w stosunku do wzorca poleceń , przy czym typ abstrakcyjny / interfejs powinien wykonywać pracę opartą na argumenty, które zostały przekazane podczas tworzenia implementacji).

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

Możesz Exceptionbardziej sprecyzować wyjątek niestandardowy, taki jak ActionException. To tylko podstawowy przykład rozpoczęcia, reszta zależy od Ciebie.

Oto przykład, LoginActionktóry (jak sama nazwa wskazuje) loguje użytkownika. UserSama jest z kolei model danych . View jest świadoma obecnością 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.
        }
    }

}

Wzór metody fabrycznej

ActionFactoryPowinny być zgodne z metodą wzorca fabryczne . Zasadniczo powinien zapewniać metodę kreacji, która zwraca konkretną implementację typu abstrakcyjnego / interfejsu. W takim przypadku powinien zwrócić implementację Actioninterfejsu na podstawie informacji podanych w żądaniu. Na przykład metoda i pathinfo (pathinfo to część za kontekstem i ścieżką serwletu w adresie URL żądania, z wyłączeniem ciągu zapytania).

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

To actionsz kolei powinno być statyczne / Map<String, Action>obejmujące całą aplikację, które przechowuje wszystkie znane działania. Od Ciebie zależy, jak wypełnić tę mapę. Kodowanie:

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

Lub konfigurowalny na podstawie pliku konfiguracyjnego właściwości / XML w ścieżce klasy: (pseudo)

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

Lub dynamicznie w oparciu o skanowanie w ścieżce klas dla klas implementujących określony interfejs i / lub adnotację: (pseudo)

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

Pamiętaj, aby utworzyć „nic nie rób” Actionw przypadku braku mapowania. Niech na przykład zwróci bezpośrednio request.getPathInfo().substring(1)wtedy.


Inne wzory

To były dotychczas ważne wzory.

Aby przejść o krok dalej, możesz użyć wzorca Fasada, aby utworzyć Contextklasę, która z kolei otacza obiekty żądania i odpowiedzi oraz oferuje kilka wygodnych metod delegowania do obiektów żądania i odpowiedzi i Action#execute()zamiast tego przekazuje ten argument jako metodę. Dodaje to dodatkową warstwę abstrakcyjną, aby ukryć surowy API Servleta. Powinieneś więc zasadniczo zerować import javax.servlet.* deklaracje przy każdej Actionimplementacji. W kategoriach JSF to właśnie robią klasy FacesContexti ExternalContext. Konkretny przykład można znaleźć w tej odpowiedzi .

Następnie jest wzorzec stanu dla przypadku, w którym chcesz dodać dodatkową warstwę abstrakcji, aby podzielić zadania gromadzenia parametrów żądania, konwertowania ich, sprawdzania ich poprawności, aktualizowania wartości modelu i wykonywania akcji. W kategoriach JSF to właśnie LifeCyclerobi.

Następnie jest wzorzec złożony dla przypadku, w którym chcesz utworzyć widok oparty na komponencie, który można dołączyć do modelu i którego zachowanie zależy od stanu cyklu życia opartego na żądaniach. W kategoriach JSF UIComponentreprezentują to.

W ten sposób możesz stopniowo ewoluować w kierunku struktury opartej na komponentach.


Zobacz też:

BalusC
źródło
4
@masato: Możesz to zrobić na przykład w statycznym bloku inicjującym.
BalusC
1
@masato: przy okazji, jeśli chcesz je odzyskać web.xml, możesz użyć ServletContextListenerdo tego. Niech fabryka go zaimplementuje (i zarejestruje jako <listener>w web.xml) i wykona zadanie napełniania podczas contextInitialized()metody.
BalusC,
3
Wykonaj zadanie, które zamiast tego powinien wykonać „post_servlet”. Nie powinieneś mieć więcej niż jednego serwletu. Sprawy biznesowe powinny odbywać się w klasach akcji. Jeśli chcesz, aby było to nowe żądanie, wróć do innego widoku, który spowodowałby przekierowanie i wykonał zadanie w nowej akcji powiązanej z żądaniem GET.
BalusC
2
Zależy. Najłatwiej jest po prostu zrobić to poprawnie w Actionimplementacji w taki sam sposób, jak w przypadku zwykłych serwletów (zobacz także serwlety wiki, aby zobaczyć podstawowy przykład, który możesz dowolnie przebudować na jakiś Validatorinterfejs). Ale możesz to również zrobić przed wywołaniem akcji, ale jest to bardziej skomplikowane, ponieważ wymaga znajomości reguł sprawdzania poprawności dla poszczególnych wyświetleń. JSF został pokryty przez tę ofiarę required="true", validator="customValidatorName"itp w znaczników XHTML.
BalusC
2
@AndreyBotalov: sprawdź kod źródłowy frameworków MVC, takich jak JSF, Spring MVC, Wicket, Struts2 itp. Wszystkie są open source.
BalusC,
13

W pobitym wzorze MVC serwlet jest kontrolerem „C”.

Jego głównym zadaniem jest dokonanie wstępnej oceny wniosku, a następnie przesłanie przetwarzania na podstawie wstępnej oceny do konkretnego pracownika. Jednym z obowiązków pracownika może być ustawienie niektórych ziaren warstwy prezentacji i przesłanie żądania na stronę JSP w celu renderowania HTML. Tylko z tego powodu musisz przekazać obiekt żądania do warstwy usługi.

Nie zacząłem jednak pisać Servletklas surowych . Praca, którą wykonują, jest bardzo przewidywalna i pełna wiedzy, coś, co frameworku robi bardzo dobrze. Na szczęście jest wielu dostępnych, sprawdzonych w czasie kandydatów (w kolejności alfabetycznej): Apache Wicket , Java Server Faces , Spring to tylko kilka z nich.

Alexander Pogrebnyak
źródło
5

IMHO, nie ma dużej różnicy w przypadku aplikacji internetowej, jeśli spojrzeć na nią z punktu widzenia przypisania odpowiedzialności. Zachowaj jednak przejrzystość warstwy. Zachowaj wszystko na potrzeby prezentacji w warstwie prezentacji, takie jak formant i kod specyficzne dla formantów internetowych. Po prostu trzymaj swoje podmioty w warstwie biznesowej i wszystkie funkcje (takie jak dodawanie, edycja, usuwanie) itp. W warstwie biznesowej. Jednak renderowanie ich w przeglądarce do obsługi w warstwie prezentacji. W przypadku .Net wzorzec ASP.NET MVC jest bardzo dobry, jeśli chodzi o utrzymywanie oddzielonych warstw. Spójrz na wzór MVC.

Kangkan
źródło
czy możesz powiedzieć coś wprost w serwlecie?
mawia
Serwlet powinien być kontrolerem, jeśli używasz MVC.
Kangkan
3

Użyłem frameworka struts i bardzo łatwo się go nauczyć. Podczas korzystania z frameworku struts na każdej stronie Twojej witryny będą znajdować się następujące elementy.

1) Użyta akcja jest wywoływana przy każdym odświeżeniu strony HTML. Ta czynność powinna wypełnić dane w formularzu, gdy strona jest ładowana po raz pierwszy i obsługiwać interakcje między internetowym interfejsem użytkownika a warstwą biznesową. Jeśli używasz strony jsp do modyfikowania modyfikowalnego obiektu java, kopia obiektu java powinna być przechowywana w formie zamiast oryginału, aby oryginalne dane nie zostały zmodyfikowane, chyba że użytkownik zapisze stronę.

2) Formularz służący do przesyłania danych między działaniem a stroną jsp. Ten obiekt powinien składać się z zestawu programów pobierających i ustawiających atrybuty, które muszą być dostępne dla pliku jsp. Formularz ma również metodę sprawdzania poprawności danych przed utrwaleniem.

3) Strona jsp, która służy do renderowania końcowego HTML strony. Strona jsp to hybryda tagów HTML i specjalnych strutów używanych do uzyskiwania dostępu do danych w formularzu i manipulowania nimi. Chociaż struts pozwala użytkownikom wstawiać kod Java do plików jsp, powinieneś być bardzo ostrożny, ponieważ utrudnia to czytanie kodu. Kod Java wewnątrz plików jsp jest trudny do debugowania i nie może być testowany jednostkowo. Jeśli zauważysz, że piszesz więcej niż 4-5 linii kodu Java w pliku jsp, kod prawdopodobnie powinien zostać przeniesiony do akcji.

EsotericNonsense
źródło
Uwaga: W Struts 2 obiekt Form jest nazywany zamiast tego Modelem, ale działa w taki sam sposób, jak opisałem w mojej oryginalnej odpowiedzi.
EsotericNonsense
3

Doskonała odpowiedź BalusC obejmuje większość wzorców aplikacji internetowych.

Niektóre aplikacje mogą wymagać Łańcucha odpowiedzialności

W projektowaniu obiektowym wzorzec łańcucha odpowiedzialności jest wzorcem złożonym ze źródła obiektów poleceń i szeregu obiektów przetwarzania. Każdy obiekt przetwarzający zawiera logikę, która określa typy obiektów poleceń, które może obsługiwać; reszta jest przekazywana do następnego obiektu przetwarzania w łańcuchu.

Użyj case, aby użyć tego wzoru:

Gdy moduł obsługi przetwarzania żądania (polecenia) jest nieznany, a żądanie to można wysłać do wielu obiektów. Zasadniczo ustawiasz następcę na obiekt. Jeśli bieżący obiekt nie może obsłużyć żądania lub przetworzyć go częściowo i przekazać to samo żądanie do obiektu będącego następcą .

Przydatne pytania / artykuły SE:

Dlaczego miałbym używać Łańcucha Odpowiedzialności nad Dekoratorem?

Wspólne zastosowania w łańcuchu odpowiedzialności?

wzór łańcucha odpowiedzialności od Oodesign

łańcuch_odpowiedzialności z zaopatrzenia

Ravindra babu
źródło