Aplicativos baseados na Web de Design Patterns [fechados]

359

Estou projetando um aplicativo simples baseado na Web. Eu sou novo nesse domínio baseado na Web. Eu precisava de seu conselho sobre os padrões de design, como a responsabilidade deve ser distribuída entre os Servlets, os critérios para criar um novo Servlet, etc.

Na verdade, tenho poucas entidades na minha página inicial e, correspondendo a cada uma delas, temos poucas opções como adicionar, editar e excluir. Anteriormente, eu estava usando um Servlet por opções como Servlet1 para adicionar entidade1, Servlet2 para editar entidade1 e assim por diante e, dessa maneira, acabamos tendo um grande número de servlets.

Agora estamos mudando nosso design. Minha pergunta é como você escolhe exatamente como escolhe a responsabilidade de um servlet. Devemos ter um Servlet por entidade que processará todas as suas opções e encaminhará a solicitação para a camada de serviço. Ou devemos ter um servlet para toda a página que processará a solicitação de página inteira e depois a encaminhará para a camada de serviço correspondente? Além disso, o objeto de solicitação deve ser encaminhado para a camada de serviço ou não.

mawia
fonte
8
Não realmente padrões de design oficiais, mas não se esqueça PRG (pós-redirect-get) e Hijax (trabalho make sem js primeiro, então seqüestrar os links e botões com ajax)
Neil McGuigan

Respostas:

488

Um aplicativo da Web decente consiste em uma mistura de padrões de design. Vou mencionar apenas os mais importantes.


Padrão do Model View Controller

O padrão de design principal (arquitetural) que você deseja usar é o padrão Model-View-Controller . O Controlador deve ser representado por um Servlet que (in) cria / usa diretamente um Modelo e uma Visualização específicos com base na solicitação. O Modelo deve ser representado por classes Javabeanas. Isso geralmente é mais divisível no Modelo de Negócios, que contém as ações (comportamento) e no Modelo de Dados, que contém os dados (informações). A Visualização deve ser representada por arquivos JSP que têm acesso direto ao Modelo ( Dados ) pelo EL (Expression Language).

Depois, há variações baseadas em como as ações e os eventos são tratados. Os populares são:

  • MVC baseado em solicitação (ação) : este é o mais simples de implementar. O ( Negócios ) Modelo trabalha diretamente com HttpServletRequeste HttpServletResponseobjetos. Você precisa reunir, converter e validar os parâmetros de solicitação (principalmente) você mesmo. A View pode ser representada por HTML / CSS / JS simples e não mantém o estado entre solicitações. É assim que, entre outros, o Spring MVC , Struts and Stripes funciona.

  • MVC baseado em componentes : isso é mais difícil de implementar. Mas você acaba com um modelo e uma visualização mais simples, nos quais toda a API Servlet "bruta" é abstraída completamente. Você não precisa reunir, converter e validar os parâmetros de solicitação. O Controller executa esta tarefa e define os parâmetros de solicitação reunidos, convertidos e validados no Modelo . Tudo o que você precisa fazer é definir métodos de ação que funcionem diretamente com as propriedades do modelo. A Visualização é representada por "componentes" no tipo de taglibs JSP ou elementos XML que, por sua vez, geram HTML / CSS / JS. O estado da vistapara os pedidos subsequentes é mantido na sessão. Isso é particularmente útil para eventos de conversão, validação e alteração de valor no servidor. É assim que, entre outros , JSF , Wicket e Play! trabalho.

Como observação lateral, hobbying com uma estrutura MVC doméstica é um exercício de aprendizado muito bom, e eu o recomendo desde que você o mantenha para fins pessoais / privados. Mas quando você se tornar profissional, é altamente recomendável escolher uma estrutura existente em vez de reinventar a sua. Aprender uma estrutura existente e bem desenvolvida leva menos tempo a longo prazo do que desenvolver e manter uma estrutura robusta.

Na explicação detalhada abaixo, restringirei-me a solicitar MVC baseado, pois é mais fácil de implementar.


Padrão do controlador frontal ( padrão do mediador )

Primeiro, a parte Controller deve implementar o padrão Front Controller (que é um tipo especializado de padrão Mediador ). Ele deve consistir em apenas um servlet que fornece um ponto de entrada centralizado de todas as solicitações. Ele deve criar o Modelo com base nas informações disponíveis pela solicitação, como pathinfo ou servletpath, o método e / ou parâmetros específicos. O modelo de negócios é chamado Actionno HttpServletexemplo abaixo .

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

A execução da ação deve retornar algum identificador para localizar a exibição. O mais simples seria usá-lo como nome do arquivo JSP. Mapeie este servlet em um específico url-patternem web.xml, por exemplo /pages/*, *.doou mesmo apenas *.html.

Em caso de prefixo-padrões como, por exemplo, /pages/*você poderia então invocar URL como http://example.com/pages/register , http://example.com/pages/login , etc e fornecer /WEB-INF/register.jsp, /WEB-INF/login.jspcom as ações POST GET apropriado e . As peças register, loginetc, estão disponíveis request.getPathInfo()como no exemplo acima.

Quando você usa padrões de sufixo, como *.do, *.htmletc, pode chamar URLs como http://example.com/register.do , http://example.com/login.do , etc, e deve alterar o exemplos de código nesta resposta (também o ActionFactory) para extrair as partes registere loginem request.getServletPath()vez disso.


Padrão de estratégia

O Actiondeve seguir o padrão de estratégia . Ele precisa ser definido como um tipo de resumo / interface que deve fazer o trabalho com base nos argumentos passados do método abstrato (essa é a diferença com o padrão de comando , em que o tipo de resumo / interface deve fazer o trabalho com base no argumentos passados ​​durante a criação da implementação).

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

Você pode tornar o Exceptionmais específico com uma exceção personalizada como ActionException. É apenas um exemplo básico de kickoff, o resto é com você.

Aqui está um exemplo de um LoginActionque (como o próprio nome diz) efetua login no usuário. Por sua Uservez, é um modelo de dados . O View está ciente da presença do 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.
        }
    }

}

Padrão de método de fábrica

A ActionFactorydeve seguir o padrão Factory Method . Basicamente, ele deve fornecer um método criacional que retorne uma implementação concreta de um tipo abstrato / interface. Nesse caso, ele deve retornar uma implementação da Actioninterface com base nas informações fornecidas pela solicitação. Por exemplo, o método e pathinfo (o pathinfo é a parte após o contexto e o caminho do servlet na URL da solicitação, excluindo a cadeia de consulta).

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

Por actionssua vez, deve haver alguma estática / em todo o aplicativo, Map<String, Action>que contém todas as ações conhecidas. Cabe a você como preencher este mapa. Codificação rígida:

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

Ou configurável com base em um arquivo de propriedades / configuração XML no caminho de classe: (pseudo)

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

Ou dinamicamente, com base em uma varredura no caminho de classe, para classes que implementam uma certa interface e / ou anotação: (pseudo)

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

Lembre-se de criar um "não fazer nada" Actionpara o caso em que não há mapeamento. Deixe, por exemplo, retornar diretamente o request.getPathInfo().substring(1)então.


Outros padrões

Esses eram os padrões importantes até agora.

Para dar um passo adiante, você pode usar o padrão Facade para criar uma Contextclasse que, por sua vez, agrupa os objetos de solicitação e resposta e oferece vários métodos de conveniência para delegar aos objetos de solicitação e resposta e passar isso como argumento para o Action#execute()método. Isso adiciona uma camada abstrata extra para ocultar a API Servlet bruta. Você deve basicamente terminar com zero import javax.servlet.* declarações em todas as Actionimplementações. Em termos de JSF, é isso que as classes FacesContexte ExternalContextestão fazendo. Você pode encontrar um exemplo concreto nesta resposta .

Depois, há o padrão State para o caso em que você deseja adicionar uma camada de abstração extra para dividir as tarefas de reunir os parâmetros de solicitação, convertê-los, validá-los, atualizar os valores do modelo e executar as ações. Em termos de JSF, é isso que LifeCycleestá fazendo.

Depois, há o padrão Composite para o caso em que você deseja criar uma visualização baseada em componente que pode ser anexada ao modelo e cujo comportamento depende do estado do ciclo de vida baseado em solicitação. Em termos JSF, é isso que UIComponentrepresenta.

Dessa forma, você pode evoluir pouco a pouco em direção a uma estrutura baseada em componentes.


Veja também:

BalusC
fonte
4
@masato: Você pode fazer isso em, por exemplo, um bloco estático de inicialização.
precisa saber é o seguinte
11
@masato: a propósito, se você gostaria de recuperá-los web.xml, então você pode usar um ServletContextListenerpara isso. Faça com que a fábrica o implemente (e registre-se como <listener>em web.xml) e faça o trabalho de enchimento durante o contextInitialized()método.
precisa saber é o seguinte
3
Faça o trabalho que o "post_servlet" deve executar na ação. Você não deve ter mais de um servlet. Coisas de negócios devem ser feitas em classes de ação. Se você deseja que ela seja uma nova solicitação, retorne a uma visão diferente que causaria um redirecionamento e faça o trabalho na nova ação associada à solicitação GET.
precisa saber é o seguinte
2
Depende. O mais fácil é fazê-lo corretamente na Actionimplementação da mesma maneira que nos servlets normais (consulte também o wiki de servlets para obter um exemplo básico, que você pode refatorar ainda mais em alguma Validatorinterface). Mas você também pode fazê-lo antes de invocar a ação, mas isso é mais complexo, pois exige que as regras de validação sejam conhecidas por visualização. JSF cobriu isso, oferecendo required="true", validator="customValidatorName", etc na marcação XHTML.
BalusC
2
@AndreyBotalov: verifique o código fonte das estruturas MVC como JSF, Spring MVC, Wicket, Struts2, etc. Todas elas são de código aberto.
BalusC
13

No padrão de MVC, o Servlet é "C" - controlador.

Sua principal tarefa é fazer a avaliação da solicitação inicial e, em seguida, despachar o processamento com base na avaliação inicial para o trabalhador específico. Uma das responsabilidades do trabalhador pode ser configurar alguns beans da camada de apresentação e encaminhar a solicitação para a página JSP para renderizar HTML. Portanto, apenas por esse motivo, você precisa passar o objeto de solicitação para a camada de serviço.

Porém, eu não começaria a escrever Servletaulas brutas . O trabalho que eles fazem é muito previsível e padronizado, algo que o framework faz muito bem. Felizmente, existem muitos candidatos disponíveis e testados pelo tempo (em ordem alfabética): Apache Wicket , Java Server Faces , Spring , entre outros.

Alexander Pogrebnyak
fonte
5

IMHO, não há muita diferença no caso de aplicação web, se você olhar para ela do ângulo de atribuição de responsabilidade. No entanto, mantenha a clareza na camada. Mantenha qualquer coisa puramente para a finalidade da apresentação na camada de apresentação, como o controle e o código específico para os controles da Web. Apenas mantenha suas entidades na camada de negócios e todos os recursos (como adicionar, editar, excluir) etc. na camada de negócios. No entanto, renderizá-los no navegador para serem manipulados na camada de apresentação. Para .Net, o padrão ASP.NET MVC é muito bom em termos de manter as camadas separadas. Examine o padrão MVC.

Kangkan
fonte
você pode ser um pouco explícito no que deve acontecer no servlet?
Mawia 22/08/10
O servlet deve ser o controlador se você usar o MVC.
Kangkan
3

Eu usei o framework struts e acho bastante fácil de aprender. Ao usar a estrutura do struts, cada página do seu site terá os seguintes itens.

1) Uma ação usada é chamada toda vez que a página HTML é atualizada. A ação deve preencher os dados no formulário quando a página é carregada pela primeira vez e manipula interações entre a interface da web da web e a camada de negócios. Se você estiver usando a página jsp para modificar um objeto java mutável, uma cópia do objeto java deve ser armazenada no formulário e não no original, para que os dados originais não sejam modificados, a menos que o usuário salve a página.

2) O formulário usado para transferir dados entre a ação e a página jsp. Este objeto deve consistir em um conjunto de getter e setters para atributos que precisam estar acessíveis para o arquivo jsp. O formulário também possui um método para validar dados antes que eles persistam.

3) Uma página jsp que é usada para renderizar o HTML final da página. A página jsp é um híbrido de HTML e tags struts especiais usadas para acessar e manipular dados no formulário. Embora o struts permita que os usuários insiram o código Java nos arquivos jsp, você deve ser muito cauteloso ao fazer isso, pois isso dificulta sua leitura. O código Java nos arquivos jsp é difícil de depurar e não pode ser testado em unidade. Se você estiver escrevendo mais de 4-5 linhas de código java dentro de um arquivo jsp, o código provavelmente deve ser movido para a ação.

EsotericNonsense
fonte
Nota: Nos suportes 2, o objeto Form é chamado de modelo, mas funciona da mesma maneira que descrevi na minha resposta original.
EsotericNonsense
3

A excelente resposta do BalusC cobre a maioria dos padrões para aplicativos da web.

Alguns aplicativos podem exigir Chain-of-responsável_pattern

No design orientado a objetos, o padrão de cadeia de responsabilidade é um padrão de design que consiste em uma fonte de objetos de comando e uma série de objetos de processamento. Cada objeto de processamento contém lógica que define os tipos de objetos de comando que ele pode manipular; o restante é passado para o próximo objeto de processamento na cadeia.

Caso de uso para usar este padrão:

Quando o manipulador para processar uma solicitação (comando) é desconhecido e essa solicitação pode ser enviada para vários objetos. Geralmente você define o sucessor como objeto. Se o objeto atual não puder manipular a solicitação ou processá-la parcialmente e encaminhe a mesma solicitação ao objeto sucessor .

Perguntas / artigos úteis sobre SE:

Por que eu usaria uma Cadeia de Responsabilidade em vez de um Decorador?

Usos comuns para cadeia de responsabilidade?

padrão de cadeia de responsabilidade da oodesign

chain_of_responsibility da criação de código-fonte

Ravindra babu
fonte