image

Access unlimited bootcamps and 650+ courses

50
%OFF
Article image
William Silva
William Silva28/02/2025 08:27
Share

Design Patterns Essenciais para Desenvolvimento Web com Java

  • #Java

🚀 Quer criar aplicações web robustas e escaláveis com Java? Os design patterns são a chave para organizar seu código, torná-lo reutilizável e preparado para os desafios do desenvolvimento moderno. Neste artigo, você vai descobrir os padrões essenciais divididos em três categorias – criacionais, estruturais e comportamentais – com exemplos práticos em Java para web. Preparado para elevar suas habilidades? Vamos mergulhar!

🎯 Por que Design Patterns no Desenvolvimento Web com Java?

Java é uma linguagem poderosa para aplicações web, usada por gigantes como Netflix e LinkedIn. Mas, sem uma boa arquitetura, seu código pode virar um Frankenstein! Os padrões de projeto trazem clareza, flexibilidade e manutenção simplificada. Aqui, exploraremos os tipos principais e como aplicá-los em cenários web reais.

Tipos de Design Patterns: Uma Visão Geral

Os padrões de projeto são classificados em três categorias principais:

  • Criacionais: Gerenciam a criação de objetos, evitando instâncias desnecessárias ou complexas.
  • Estruturais: Organizam a composição e relacionamento entre objetos.
  • Comportamentais: Facilitam a comunicação e interação entre objetos.

image

💡 Agora, vamos detalhar cada categoria com exemplos práticos!

1. Padrões Criacionais

Esses padrões ajudam a criar objetos de forma controlada e flexível. Aqui estão os principais:

1.1 Factory Method

image

Descrição: Permite criar objetos sem especificar a classe concreta, delegando a criação a um método "fábrica". Ideal para APIs REST ou serviços web dinâmicos.

Árvore de Diretórios:

📂 src/main/java
📂 com/example/patterns
  📂 factory
    - Product.java
    - ConcreteProductA.java
    - ConcreteProductB.java
    - ProductFactory.java

Exemplo em Java (Web):

// Interface do produto
interface Product {
  String getDescription();
}

// Produto concreto A
class ConcreteProductA implements Product {
  public String getDescription() {
      return "Produto A para API de pedidos";
  }
}

// Produto concreto B
class ConcreteProductB implements Product {
  public String getDescription() {
      return "Produto B para API de pagamentos";
  }
}

// Fábrica
class ProductFactory {
  public Product createProduct(String type) {
      if ("A".equals(type)) return new ConcreteProductA();
      if ("B".equals(type)) return new ConcreteProductB();
      return null;
  }
}

// Uso em um Servlet (web)
import jakarta.servlet.http.*;
import java.io.*;

public class ProductServlet extends HttpServlet {
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
      ProductFactory factory = new ProductFactory();
      Product product = factory.createProduct(req.getParameter("type"));
      resp.getWriter().write(product.getDescription());
  }
}

Aplicação: Um Servlet que retorna diferentes respostas com base no parâmetro type.

Explicação Passo a Passo do Código Citado:

  • interface Product: Define o contrato que todos os produtos devem seguir, com o método getDescription().
  • ConcreteProductA e ConcreteProductB: Implementam a interface, cada uma retornando uma descrição específica.
  • ProductFactory: O método createProduct recebe um parâmetro type e usa condicionais para decidir qual classe concreta instanciar.
  • ProductServlet: Um Servlet web que lê o parâmetro type da requisição HTTP, usa a fábrica para criar o produto e escreve a descrição na resposta.

Por que Isso é Útil?

  • Permite adicionar novos tipos de produtos (ex.: ConcreteProductC) apenas ajustando a fábrica, sem mudar o Servlet.
  • Centraliza a lógica de criação, tornando o código mais limpo e fácil de testar.

1.2 Singleton

image

Descrição: Garante uma única instância de uma classe, útil para conexões de banco de dados em aplicações web.

Árvore de Diretórios:

📂 src/main/java
📂 com/example/patterns
  📂 singleton
    - DatabaseConnection.java

Exemplo em Java (Web):

public class DatabaseConnection {
  private static DatabaseConnection instance;

  private DatabaseConnection() {} // Construtor privado

  public static DatabaseConnection getInstance() {
      if (instance == null) {
          instance = new DatabaseConnection();
      }
      return instance;
  }

  public void connect() {
      System.out.println("Conectado ao banco de dados!");
  }
}

// Uso em um serviço web
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("/db")
public class DatabaseResource {
  @GET
  public String connectToDb() {
      DatabaseConnection db = DatabaseConnection.getInstance();
      db.connect();
      return "Conexão estabelecida!";
  }
}

Aplicação: Um endpoint REST que usa uma única conexão ao banco.

Explicação Passo a Passo do Código Citado:

  • DatabaseConnection: O construtor é privado para evitar instâncias diretas.
  • getInstance(): Verifica se instance é nulo; se for, cria uma nova instância; caso contrário, retorna a existente.
  • connect(): Simula uma conexão ao banco (em um caso real, usaria JDBC ou JPA).
  • DatabaseResource: Um recurso REST que obtém a instância única e a usa para conectar, retornando uma mensagem.

Por que Isso é Útil?

  • Economiza recursos ao reutilizar uma única conexão ao banco em toda a aplicação.
  • Evita problemas de concorrência em cenários web com múltiplas requisições.

2. Padrões Estruturais

Focam na organização e composição de objetos. Vamos aos principais:

2.1 Adapter

image

Descrição: Converte a interface de uma classe em outra esperada pelo cliente. Útil para integrar APIs externas em sistemas web.

Árvore de Diretórios:

📂 src/main/java
📂 com/example/patterns
  📂 adapter
    - LegacySystem.java
    - NewSystem.java
    - SystemAdapter.java

Exemplo em Java (Web):

// Sistema legado
class LegacySystem {
  public String getOldData() {
      return "Dados antigos";
  }
}

// Nova interface esperada
interface NewSystem {
  String getNewData();
}

// Adaptador
class SystemAdapter implements NewSystem {
  private LegacySystem legacy;

  public SystemAdapter(LegacySystem legacy) {
      this.legacy = legacy;
  }

  public String getNewData() {
      return legacy.getOldData() + " adaptados!";
  }
}

// Uso em um Controller Spring
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AdapterController {
  @GetMapping("/data")
  public String getData() {
      LegacySystem legacy = new LegacySystem();
      NewSystem adapter = new SystemAdapter(legacy);
      return adapter.getNewData();
  }
}

Aplicação: Integração de sistemas legados em uma API Spring moderna.

Explicação Passo a Passo do Código Citado:

  • LegacySystem: Representa um sistema antigo com o método getOldData().
  • NewSystem: Interface que o cliente espera, com getNewData().
  • SystemAdapter: Implementa NewSystem, recebe uma instância de LegacySystem e adapta sua saída.
  • AdapterController: Um endpoint Spring que usa o adaptador para integrar o sistema legado e retorna o resultado.

Por que Isso é Útil?

  • Permite reutilizar código antigo sem reescrevê-lo, economizando tempo.
  • Facilita a integração com APIs ou bibliotecas externas em projetos web.

2.2 Decorator

image

Descrição: Adiciona funcionalidades a objetos dinamicamente. Perfeito para personalizar respostas em APIs.

Árvore de Diretórios:

📂 src/main/java
📂 com/example/patterns
  📂 decorator
    - Response.java
    - BasicResponse.java
    - JsonResponseDecorator.java

Exemplo em Java (Web):

interface Response {
  String render();
}

class BasicResponse implements Response {
  public String render() {
      return "Resposta básica";
  }
}

class JsonResponseDecorator implements Response {
  private Response wrapped;

  public JsonResponseDecorator(Response wrapped) {
      this.wrapped = wrapped;
  }

  public String render() {
      return "{\"data\": \"" + wrapped.render() + "\"}";
  }
}

// Uso em um Servlet
public class DecoratorServlet extends HttpServlet {
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
      Response response = new JsonResponseDecorator(new BasicResponse());
      resp.getWriter().write(response.render());
  }
}

Aplicação: Formata respostas como JSON dinamicamente.

Explicação Passo a Passo do Código Citado:

  • Response: Interface base com o método render().
  • BasicResponse: Implementação simples que retorna uma string básica.
  • JsonResponseDecorator: Recebe um Response, "embrulha" e adiciona formatação JSON ao resultado.
  • DecoratorServlet: Um Servlet que aplica o decorador e envia a resposta formatada.

Por que Isso é Útil?

  • Permite adicionar comportamentos (ex.: XML, log) sem alterar o núcleo da classe original.
  • Ideal para respostas personalizadas em APIs REST de forma modular.

3. Padrões Comportamentais

Gerenciam a interação entre objetos. Veja os destaques:

3.1 Observer

image

Descrição: Define uma dependência um-para-muitos, notificando objetos sobre mudanças. Ideal para sistemas de eventos web.

Árvore de Diretórios:

📂 src/main/java
📂 com/example/patterns
  📂 observer
    - Subject.java
    - Observer.java
    - OrderSubject.java
    - EmailObserver.java

Exemplo em Java (Web):

import java.util.ArrayList;
import java.util.List;

interface Observer {
  void update(String message);
}

class OrderSubject {
  private List<Observer> observers = new ArrayList<>();

  public void addObserver(Observer o) {
      observers.add(o);
  }

  public void notifyObservers(String message) {
      for (Observer o : observers) {
          o.update(message);
      }
  }

  public void newOrder() {
      notifyObservers("Novo pedido criado!");
  }
}

class EmailObserver implements Observer {
  public void update(String message) {
      System.out.println("Enviando e-mail: " + message);
  }
}

// Uso em um endpoint
@Path("/order")
public class OrderResource {
  @GET
  public String createOrder() {
      OrderSubject subject = new OrderSubject();
      subject.addObserver(new EmailObserver());
      subject.newOrder();
      return "Pedido criado!";
  }
}

Aplicação: Notifica usuários por e-mail sobre novos pedidos.

Explicação Passo a Passo do Código Citado:

  • Observer: Interface com o método update() para receber notificações.
  • OrderSubject: Mantém uma lista de observadores e os notifica via notifyObservers().
  • EmailObserver: Implementa Observer, simulando o envio de um e-mail.
  • OrderResource: Um endpoint REST que cria um pedido e dispara a notificação.

Por que Isso é Útil?

  • Desacopla quem gera eventos (subject) de quem os consome (observers).
  • Perfeito para sistemas web com notificações em tempo real ou eventos assíncronos.

3.2 Strategy

image

Descrição: Permite trocar algoritmos dinamicamente. Útil para validações em formulários web.

Árvore de Diretórios:

📂 src/main/java
📂 com/example/patterns
  📂 strategy
    - ValidationStrategy.java
    - EmailValidation.java
    - PhoneValidation.java
    - Validator.java

Exemplo em Java (Web):

interface ValidationStrategy {
  boolean validate(String input);
}

class EmailValidation implements ValidationStrategy {
  public boolean validate(String input) {
      return input.contains("@");
  }
}

class PhoneValidation implements ValidationStrategy {
  public boolean validate(String input) {
      return input.matches("\\d{9}");
  }
}

class Validator {
  private ValidationStrategy strategy;

  public Validator(ValidationStrategy strategy) {
      this.strategy = strategy;
  }

  public boolean execute(String input) {
      return strategy.validate(input);
  }
}

// Uso em um Controller
import org.springframework.web.bind.annotation.*;

@RestController
public class ValidationController {
  @GetMapping("/validate")
  public String validateInput(@RequestParam String type, @RequestParam String input) {
      Validator validator = new Validator("email".equals(type) ? new EmailValidation() : new PhoneValidation());
      return validator.execute(input) ? "Válido" : "Inválido";
  }
}

Aplicação: Valida entradas de formulários com estratégias intercambiáveis.

Explicação Passo a Passo do Código Citado:

  • ValidationStrategy: Interface que define o método validate().
  • EmailValidation e PhoneValidation: Implementações concretas com regras específicas.
  • Validator: Recebe uma estratégia no construtor e a executa em execute().
  • ValidationController: Endpoint Spring que escolhe a estratégia com base no parâmetro type.

Por que Isso é Útil?

  • Facilita a troca de algoritmos sem alterar o código cliente.
  • Ideal para validações dinâmicas em formulários web ou regras de negócio mutáveis.

🚀 Conclusão: Java + Design Patterns = Sucesso na Web

Os padrões criacionais, estruturais e comportamentais são ferramentas poderosas para construir aplicações web escaláveis e manuteníveis com Java. Usando desde o Factory Method para criar APIs dinâmicas ao Strategy para validações flexíveis, essas técnicas vão te destacar como desenvolvedor. Que tal aplicar um desses padrões no seu próximo projeto?

📖 Dica de leitura

Refactoring.Guru : https://refactoring.guru/design-patterns

📌 Sobre o Autor

William Lima

Graduando em Análise e Desenvolvimento de Sistemas, sempre em busca constante por aprendizado e novas experiências. Meus hobbies incluem artes marciais, jogos online (especialmente MMORPG, fighting games e retro games).

💼 Conecte-se comigo:

  • LinkedIn: https://www.linkedin.com/in/williamlimasilva/
  • GitHub: https://github.com/williamlimasilva

Share
Recommended for you
Deal - Spring Boot e Angular (17+)
Cognizant - Arquitetura com Spring Boot e Cloud
Claro - Java com Spring Boot
Comments (0)