Applicazioni basate sul Web di Design Patterns [chiuso]


359

Sto progettando una semplice applicazione basata sul web. Sono nuovo di questo dominio basato sul Web. Avevo bisogno del tuo consiglio per quanto riguarda i modelli di progettazione come la distribuzione delle responsabilità tra i servlet, i criteri per la creazione di nuovi servlet, ecc.

In realtà, ho poche entità sulla mia home page e in corrispondenza di ognuna di esse abbiamo poche opzioni come aggiungere, modificare ed eliminare. Prima usavo un Servlet per opzioni come Servlet1 per aggiungere entity1, Servlet2 per modificare entity1 e così via e in questo modo abbiamo finito per avere un gran numero di servlet.

Ora stiamo cambiando il nostro design. La mia domanda è come si sceglie esattamente come si sceglie la responsabilità di un servlet. Dovremmo avere un Servlet per entità che elaborerà tutte le sue opzioni e inoltrerà la richiesta al livello di servizio. O dovremmo avere un servlet per l'intera pagina che elaborerà l'intera richiesta di pagina e poi la inoltrerà al livello di servizio corrispondente? Inoltre, l'oggetto richiesta deve essere inoltrato al livello di servizio o meno.


8
Non proprio modelli di design ufficiali, ma non dimenticare PRG (post-redirect-get) e Hijax (prima fai un lavoro senza js, quindi dirotta link e pulsanti con ajax)
Neil McGuigan

Risposte:


488

Un'applicazione web un po 'decente consiste in un mix di modelli di design. Citerò solo quelli più importanti.


Modello Controller vista modello

Il modello di progettazione (architettonico) di base che desideri utilizzare è il modello Model-View-Controller . Il controller deve essere rappresentato da un servlet che (in) crea / utilizza direttamente un modello e una vista specifici in base alla richiesta. Il modello deve essere rappresentato da classi giavabee. Ciò è spesso più divisibile nel modello di business che contiene le azioni (comportamento) e nel modello di dati che contiene i dati (informazioni). La vista è di essere rappresentato da file JSP che hanno accesso diretto al ( dati ) del modello da EL (Expression Language).

Quindi, ci sono variazioni basate su come vengono gestite le azioni e gli eventi. I più popolari sono:

  • MVC basato su richiesta (azione) : questo è il più semplice da implementare. L'( Affari ) Modello lavora direttamente con HttpServletRequeste HttpServletResponseoggetti. Devi raccogliere, convertire e convalidare (principalmente) i parametri della richiesta. La vista può essere rappresentata da HTML / CSS / JS semplicemente vaniglia e non mantiene lo stato tra le richieste. Ecco come funziona Spring MVC , Struts and Stripes .

  • MVC basato su componenti : è più difficile da implementare. Ma si finisce con un modello più semplice e si vede in cui tutta l'API Servlet "grezza" viene sottratta completamente. Non dovresti avere la necessità di raccogliere, convertire e validare tu stesso i parametri della richiesta. Il controller esegue questa attività e imposta i parametri di richiesta raccolti, convertiti e convalidati nel modello . Tutto quello che devi fare è definire i metodi di azione che funzionano direttamente con le proprietà del modello. La vista è rappresentata da "componenti" nel sapore di taglibs JSP o elementi XML che a loro volta generano HTML / CSS / JS. Lo stato della vista per le richieste successive viene mantenuta nella sessione. Ciò è particolarmente utile per eventi di conversione, convalida e modifica del valore sul lato server. Ecco come, tra gli altri , JSF , Wicket e Play! lavori.

Come nota a margine, andare in giro con un framework MVC nostrano è un esercizio di apprendimento molto bello, e lo raccomando finché lo tieni per scopi personali / privati. Ma una volta diventato professionale, si consiglia vivamente di scegliere un framework esistente piuttosto che reinventare il proprio. L'apprendimento di un framework esistente e ben sviluppato richiede a lungo termine meno tempo rispetto allo sviluppo e al mantenimento di un framework robusto da soli.

Nella spiegazione dettagliata di seguito mi limiterò a richiedere MVC basato su richiesta poiché è più facile da implementare.


Modello controller frontale ( modello mediatore )

Innanzitutto, la parte Controller dovrebbe implementare il modello Front Controller (che è un tipo specializzato di modello Mediatore ). Dovrebbe consistere in un solo servlet che fornisce un punto di accesso centralizzato per tutte le richieste. Dovrebbe creare il modello in base alle informazioni disponibili dalla richiesta, come pathinfo o servletpath, il metodo e / o parametri specifici. Il modello aziendale viene chiamato Actionnell'esempio seguente 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);
    }
}

L'esecuzione dell'azione dovrebbe restituire un identificatore per individuare la vista. Il più semplice sarebbe usarlo come nome file del JSP. Visualizza la servlet su una specifica url-patternin web.xml, per esempio /pages/*, *.doo anche solo *.html.

In caso di schemi di prefissi, ad esempio, /pages/*è possibile richiamare URL come http://example.com/pages/register , http://example.com/pages/login , ecc. E fornire /WEB-INF/register.jsp, /WEB-INF/login.jspcon le azioni GET e POST appropriate . Le parti register, loginecc. Sono quindi disponibili request.getPathInfo()come nell'esempio sopra.

Quando si utilizzano schemi di suffissi come *.do, *.htmlecc., È quindi possibile richiamare URL come http://esempio.com/register.do , http://esempio.com/login.do , ecc. E si dovrebbe modificare il esempi di codice in questa risposta (anche il ActionFactory) per estrarre invece le parti registere .loginrequest.getServletPath()


Modello di strategia

L' Actiondovrebbe seguire il modello di strategia . Deve essere definito come un tipo astratto / di interfaccia che dovrebbe fare il lavoro in base agli argomenti passati del metodo astratto (questa è la differenza con il modello di comando , in cui il tipo astratto / di interfaccia dovrebbe fare il lavoro in base al argomenti che sono stati passati durante la creazione dell'implementazione).

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

Potresti voler rendere il Exceptionpiù specifico con un'eccezione personalizzata come ActionException. È solo un esempio di kickoff di base, il resto dipende da te.

Ecco un esempio di un LoginActionche (come dice il nome) accede all'utente. Lo Userstesso è a sua volta un modello di dati . The View è a conoscenza della presenza di 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.
        }
    }

}

Modello del metodo di fabbrica

L' ActionFactorydovrebbe seguire il metodo di pattern Factory . Fondamentalmente, dovrebbe fornire un metodo creativo che restituisce un'implementazione concreta di un tipo astratto / interfaccia. In questo caso, dovrebbe restituire un'implementazione Actiondell'interfaccia basata sulle informazioni fornite dalla richiesta. Ad esempio, il metodo e pathinfo (pathinfo è la parte dopo il contesto e il percorso servlet nell'URL della richiesta, esclusa la stringa di query).

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

A actionssua volta dovrebbe essere un po 'statico / a livello di applicazione Map<String, Action>che contiene tutte le azioni conosciute. Sta a te come riempire questa mappa. brutalmente:

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

O configurabile in base a un file di configurazione proprietà / XML nel percorso di classe: (pseudo)

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

O basato dinamicamente su una scansione nel percorso di classe per le classi che implementano una certa interfaccia e / o annotazione: (pseudo)

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

Ricorda di creare un "non fare nulla" Actionper il caso in cui non ci sia mappatura. Lascia che ad esempio restituisca direttamente request.getPathInfo().substring(1)allora.


Altri schemi

Questi erano gli schemi importanti finora.

Per fare un passo ulteriore, è possibile utilizzare il modello Facade per creare una Contextclasse che a sua volta avvolge gli oggetti richiesta e risposta e offre diversi metodi di convenienza delegando gli oggetti richiesta e risposta e passandoli invece come argomento nel Action#execute()metodo. Ciò aggiunge un ulteriore livello astratto per nascondere l'API Servlet non elaborata. Si dovrebbe quindi sostanzialmente finire con zero import javax.servlet.* dichiarazioni in ogni Actionimplementazione. In termini di JSF, questo è ciò FacesContextche ExternalContextstanno facendo le classi e . Puoi trovare un esempio concreto in questa risposta .

Quindi c'è il modello di stato nel caso in cui desideri aggiungere un ulteriore livello di astrazione per dividere le attività di raccolta dei parametri di richiesta, conversione, convalida, aggiornamento dei valori del modello ed esecuzione delle azioni. In termini di JSF, questo è ciò che LifeCyclesta facendo.

Quindi c'è il modello composito per il caso in cui si desidera creare una vista basata su componenti che può essere collegata al modello e il cui comportamento dipende dallo stato del ciclo di vita basato sulla richiesta. In termini di JSF, questo è ciò che UIComponentrappresentano.

In questo modo è possibile evolvere a poco a poco verso un framework basato su componenti.


Guarda anche:


4
@masato: potresti farlo ad esempio in un blocco di inizializzatore statico.
BalusC

1
@masato: a proposito, se desideri recuperarli da web.xml, puoi usare a ServletContextListenerper questo. Chiedere alla fabbrica di implementarlo (e registrarsi come <listener>in web.xml) ed eseguire il lavoro di riempimento durante il contextInitialized()metodo.
BalusC,

3
Esegui il lavoro che invece dovrebbe fare "post_servlet" nell'azione. Non dovresti avere più di un servlet. Le attività devono essere svolte in classi di azione. Se desideri che sia una nuova richiesta, torna a una vista diversa che provocherebbe un reindirizzamento e farebbe il lavoro nella nuova azione associata alla richiesta GET.
BalusC,

2
Dipende. Il più semplice è farlo nel modo giusto Actionnell'implementazione allo stesso modo dei normali servlet (vedi anche wiki dei servlet per un esempio di base, che sei libero di rifattorizzare ulteriormente in qualche Validatorinterfaccia). Ma potresti anche farlo prima di invocare l'azione, ma questo è più complesso in quanto richiede che le regole di convalida siano note in base alla vista. JSF ha coperto questo offrendo required="true", validator="customValidatorName"ecc nel markup XHTML.
BalusC

2
@AndreyBotalov: controlla il codice sorgente dei framework MVC come JSF, Spring MVC, Wicket, Struts2, ecc. Sono tutti open source.
BalusC,

13

Nel modello MVC mal battuto, il Servlet è "C" - controller.

Il suo compito principale è fare la valutazione della richiesta iniziale e quindi inviare l'elaborazione in base alla valutazione iniziale al lavoratore specifico. Una delle responsabilità del lavoratore potrebbe essere quella di impostare alcuni bean del livello di presentazione e inoltrare la richiesta alla pagina JSP per eseguire il rendering HTML. Pertanto, solo per questo motivo, è necessario passare l'oggetto richiesta al livello di servizio.

Non vorrei però iniziare a scrivere Servletlezioni crude . Il lavoro che svolgono è molto prevedibile e stimolante, qualcosa che il framework fa molto bene. Fortunatamente, ci sono molti candidati disponibili, testati nel tempo (in ordine alfabetico): Apache Wicket , Java Server Faces , Spring per citarne alcuni.


5

IMHO, non c'è molta differenza nel caso di un'applicazione web se la guardi dal punto di vista dell'assegnazione di responsabilità. Tuttavia, mantieni la chiarezza nel livello. Mantieni qualsiasi cosa a puro scopo di presentazione nel livello presentazione, come il controllo e il codice specifici per i controlli web. Tieni semplicemente le tue entità nel livello aziendale e tutte le funzionalità (come aggiungi, modifica, elimina) ecc. Nel livello aziendale. Tuttavia, eseguendone il rendering sul browser per la gestione nel livello di presentazione. Per .Net, il modello ASP.NET MVC è molto buono in termini di separazione dei livelli. Guarda il modello MVC.


puoi andare un po 'esplicito in cosa dovrebbe andare in servlet?
mawia,

Il servlet dovrebbe essere il controller se si utilizza MVC.
Kangkan,

3

Ho usato il framework struts e lo trovo abbastanza facile da imparare. Quando si utilizza il framework struts ogni pagina del sito avrà i seguenti elementi.

1) Un'azione utilizzata viene chiamata ogni volta che la pagina HTML viene aggiornata. L'azione dovrebbe popolare i dati nel modulo quando la pagina viene caricata per la prima volta e gestisce le interazioni tra l'interfaccia utente Web e il livello aziendale. Se si utilizza la pagina jsp per modificare un oggetto java mutabile, una copia dell'oggetto java deve essere archiviata nel modulo anziché nell'originale in modo che i dati originali non vengano modificati a meno che l'utente non salvi la pagina.

2) Il modulo utilizzato per trasferire i dati tra l'azione e la pagina jsp. Questo oggetto dovrebbe essere costituito da un insieme di getter e setter per gli attributi che devono essere accessibili al file jsp. Il modulo ha anche un metodo per convalidare i dati prima che vengano mantenuti.

3) Una pagina jsp che viene utilizzata per eseguire il rendering dell'HTML finale della pagina. La pagina jsp è un ibrido di HTML e tag struts speciali utilizzati per accedere e manipolare i dati nel modulo. Sebbene struts consenta agli utenti di inserire codice Java nei file jsp, dovresti essere molto cauto nel farlo perché rende il tuo codice più difficile da leggere. Il codice Java all'interno dei file jsp è difficile da eseguire il debug e non può essere testato dall'unità. Se ti ritrovi a scrivere più di 4-5 righe di codice java all'interno di un file jsp, il codice dovrebbe probabilmente essere spostato nell'azione.


Nota: negli struts 2 l'oggetto Form viene chiamato invece Modello ma funziona nello stesso modo in cui ho descritto nella mia risposta originale.
EsotericNonsense,

3

La risposta eccellente di BalusC copre la maggior parte dei modelli per le applicazioni web.

Alcune applicazioni potrebbero richiedere Catena di responsabilità

Nella progettazione orientata agli oggetti, il modello di catena di responsabilità è un modello di progettazione costituito da una fonte di oggetti di comando e una serie di oggetti di elaborazione. Ogni oggetto di elaborazione contiene una logica che definisce i tipi di oggetti comando che può gestire; il resto viene passato all'oggetto di elaborazione successivo nella catena.

Usa il caso per usare questo modello:

Quando il gestore per elaborare una richiesta (comando) è sconosciuto e questa richiesta può essere inviata a più oggetti. Generalmente si imposta il successore sull'oggetto. Se l'oggetto corrente non è in grado di gestire la richiesta o elaborare parzialmente la richiesta e inoltrare la stessa richiesta all'oggetto successivo .

Domande / articoli utili su SE:

Perché dovrei mai usare una catena di responsabilità su un decoratore?

Usi comuni per la catena di responsabilità?

modello di catena di responsabilità di Oodesign

chain_of_responsibility dal sourcemaking

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.