image

Acesse bootcamps ilimitados e +650 cursos pra sempre

60
%OFF
Article image
Gustavo Coimbra
Gustavo Coimbra17/03/2025 23:22
Compartilhe
Nexa - Análise Avançada de Imagens e Texto com IA na AWSRecomendados para vocêNexa - Análise Avançada de Imagens e Texto com IA na AWS

Design Patterns: O Segredo para Projetos de Software Duradouros e Flexíveis

  • #Arquitetura de Sistemas
  • #Design Patterns
  • #Java
  • #POO

Design Patterns: O Segredo para Projetos de Software Duradouros e Flexíveis

Introdução:

  • E aí desenvolvedor já se deparou em algum projeto onde a base de código está confusa, repetindo códigos de maneira desnecessária? Ou até mesmo de dar manutenção, onde uma simples alteração pode quebrar o código inteiro. 😨🤯
  • Pois bem, é para isto que os Design Patterns (ou Padrões de Projeto em português) vem para ajudar. 😎👨‍💻
  • Meu nome é Gustavo Coimbra, atualmente estou no 7º semestre de Sistemas de Informações e vou explicar o que são os designs patterns, por que aprendê-los e alguns exemplos. Isto foi um tópico que foi discutido na matéria de Arquitetura de Software que comecei a ter maior interesse e apresentar eles a que ainda não conhece.

O que são os design patterns?

  •  Os design patterns são soluções comuns para problemas que acontecem normalmente em programas que utilizam o paradigma da Programação Orientada a Objetos (POO ou OOP em inglês).
  • Ficou famoso com o livro “Design Patterns: Elements of Reusable Object-Oriented Software” publicado pelo GoF (Gang of Four), onde os autores descrevem 23 padrões que resolviam problemas comuns no processo de desenvolvimento de software orientado a objetos. https://en.wikipedia.org/wiki/Design_Patterns

Por que aprendê-los?

  • Os design patterns são um conjunto de soluções testadas para problemas comuns durante o desenvolvimento de uma aplicação.
  • Facilita a manutenção do código
  • Facilidade na comunicação do time, exemplo você pode falar que um Strategy (design pattern) já resolveria o problema que o time precisa resolver.
  • Flexibilidade, com poucas alterações dependendo do design pattern já é possível implementar uma funcionalidade nova.
  • Compreensão do código, com eles aplicados fica mais fácil a leitura e entendimento para novas pessoas que entrarem no time e para futuramente se precisar alterar.

Tipos de design patterns:

  •   Há 3 principais tipos que variam de acordo com o propósito de cada padrão, por exemplo:
  • Padrões Criacionais (Creational Patterns): que buscam disponibilizar a criação de objetos mais flexível.
  • Padrões estruturais (Structural Patterns): que se referem mais a como as classes do código irão se estruturar e mantê-las flexíveis
  • Padrões Comportamentais (Behavioral patterns): que vão lidar com o comportamento do objeto em determinada ocasião
  • Tipos de Design Patterns abaixo site Refactoring Guru: https://refactoring.guru/design-patterns/catalog

image

Exemplos em JAVA, mas pode serem aplicados a qualquer linguagem que suporta a POO:

Padrões criacionais:

  • Factory:
  • O factory veio para eliminar a necessidade de se utilizar a palavra-chave new, nas pontas do programa, através de uma interface padrão. Funcionando como uma fábrica mesmo.
  • Sua classe que utiliza a fábrica apenas precisa saber que recebe uma interface e através da interface sem importar qual a implementação do objeto gerado executa a instrução desejada.
  • Imagina que no seu projeto você tenha que criar objetos como um Carro, Motocicleta, Caminhão etc..
  • Todos eles são um tipo véiculo não? Então pode-se criar uma interface chamada Veiculo para se ter em comum com eles
//Interface padrão que vai representar um veículo
public interface Veiculo {
  public void ligar();
}
  • Após isto implementar as classes concretas (que são as classes de fato que representa o objeto criado), exemplo de uma implementação:
public class Carro implements Veiculo {
 /* ... */
  @Override
  public void ligar() {
    System.out.println("Ligando um carro...");
  }
}
  • E agora o próx. passo criar a nossa fábrica de objetos, que vai ser uma classe também:
public class VeiculoFactory {
  public static Veiculo getVeiculo(String tipo) {
    Veiculo veiculo = null;
    
      switch (tipo) {
        case "carro":
          veiculo = new Carro();
          break;
        case "motocicleta":
          veiculo = new Motocicleta();
          break;
        case "caminhao":
          veiculo = new Caminhao();
          break;
        default:
          veiculo = null;
          break;
      }
      
    return veiculo;
 }
}
  • Depois para utilizar, chamar o método estático da fábrica onde for necessário e como o método retorna uma implementação de veículo só utilizar colocar a variável como a interface recebendo um valor:
public class Main {
  public static void main(String[] args) {
      Veiculo carro = VeiculoFactory.getVeiculo("carro");
      carro.ligar();
      //output: Ligando um carro...
      Veiculo motocicleta = VeiculoFactory.getVeiculo("motocicleta");
      motocicleta.ligar();
      
      //output: Ligando uma motocicleta...
  }
}
  •  Exemplo de uma implementação mais simples deste padrão, mas há vários outros padrões para criação de objetos.

 Padrões estruturais:

  • Facade:
  • Um padrão estrutural que visa facilitar a interação com outras classes ainda mais complexas ou até várias outras apenas chamando um método.
  • Imagine que você precise instânciar várias classes durante uma operação do código e em outro lugar precise repetir o mesmo processo. Exemplo, ao ligar um computador apenas apertamos um botão para ligar e só esperar ele ligar, mas por baixo dos panos ele faz todo processo de boot até exibição do S.O. para usar. E o facade é esta ideia.
  • Exemplo: Criar um sistema de home-theater, onde através de uma só chamada vai ligar o sistema de som, de vídeo e luz
  • Exemplo classe pertencente a todo sistema de home-theater:
public class SistemaDeSom {
  public void ligar() {
    System.out.println("Sistema de som ligado!");
  }
  
  public void desligar() {
    System.out.println("Sistema de som desligado!");
  }
}
  • E após isto implementar as outras classes pertencentes ao sistema, e, por fim, fazer uma classe responsável por ativar todos esses métodos através de uma só chamada, no caso a classe do sistema inteiro de home-theater:
public class SistemaHomeTheater {
  private SistemaDeSom sistemaDeSom;
  private SistemaDeVideo sistemaDeVideo;
  private SistemaDeLuzes sistemaDeLuzes;
  
  public SistemaHomeTheater(
      SistemaDeSom sistemaDeSom,
      SistemaDeVideo sistemaDeVideo,
      SistemaDeLuzes sistemaDeLuzes
  ) {
      this.sistemaDeSom = sistemaDeSom;
      this.sistemaDeVideo = sistemaDeVideo;
      this.sistemaDeLuzes = sistemaDeLuzes;
  }
  
  public void ligar() {
      System.out.println("Sistema de Home Theater ligando!");
      sistemaDeSom.ligar();
      sistemaDeVideo.ligar();
      sistemaDeLuzes.ligar();
      System.out.println("Home Theater ligado!");
  }
  
  public void desligar() {
      System.out.println("Sistema de Home Theater desligado!");
      sistemaDeSom.desligar();
      sistemaDeVideo.desligar();
      sistemaDeLuzes.desligar();
      System.out.println("Home Theater desligado!");
  }
}
  • E pronto agora no quando esta classe de Home Theater irá ligar todos estes sub-sistemas através de uma única chamada no código que deseja utilizar este sistema.
public class Main {
  public static void main(String[] args) {
      SistemaDeSom sistemaDeSom = new SistemaDeSom();
      SistemaDeVideo sistemaDeVideo = new SistemaDeVideo();
      SistemaDeLuzes sistemaDeLuzes = new SistemaDeLuzes();
      
      SistemaHomeTheater homeTheater = new SistemaHomeTheater(sistemaDeSom, sistemaDeVideo, sistemaDeLuzes);
      
      homeTheater.ligar();
      System.out.println("Assistindo minha netflix!");
      homeTheater.desligar();
  }
}

 Padrões comportamentais:

  • Strategy:
  • Este padrão de projeto permite em tempo de execução do programa, executar variações de um mesmo algoritmo.
  • Imagine que no momento de salvar uma informação, possa escolher entre salvar num banco de dados, em disco ou em cache. Você pode ter para cada uma dessas opções estratégias que vão fazer o processo de salvar, apenas variando a implementação.
  • 1º passo definir uma interface padrão que todas as classes concretas das implementações vão ter:
public interface IDataSaveStrategy {
  public String save(String content);
}
  • 2º passo criar classes que vão implementar esta interface e a variação do algoritmo de acordo com seu contexto (estratégias):
public class SaveByDisk implements IDataSaveStrategy {
  @Override
  public String save(String content) {
      return "Salvou o seguinte conteúdo " + content + " no disco.\nProva que isto funcionou: " + this.getClass().getName();
  }
}
  • 3º passo criar uma classe chamada de contexto que vai ser responsável delegar para a classe concreta a função de executar a chamada do método da estratégia:
public class DataSaver {

  private IDataSaveStrategy strategy; /* Guarda uma referência para a classe responsável que vai salvar o conteúdo */
  
  public DataSaver() {
  }
  
  public IDataSaveStrategy getStrategy() {
    return strategy;
  }
  
  public void setStrategy(IDataSaveStrategy strategy) {
      this.strategy = strategy;
  }
  
  /* Delega para classe responsável a função de salvar com suas particularidades */
  public String save(String content) {
      return this.strategy.save(content);
  }
}
  •  4º passo no seu código “cliente” que é o que vai usar a classe de contexto, escolher a estratégia desejada e passar para a classe de contexto e que após vai delegar para a classe concreta a função de executar o método:
/* Main.java */

DataSaver context = new DataSaver(); /* Inicialização da classe contexto */
IDataSaveStrategy strategy = null;

strategy = new SaveByCache();
context.setStrategy(strategy);
context.save("Mensagem a ser salva");

System.out.println(context.save("Mensagem a ser salva")); // output: Salvou o seguinte conteúdo Mensagem a ser salva no cache.

strategy = new SaveByDisk();
context.setStrategy(strategy);
System.out.println(context.save("Mensagem a ser salva")); // output Salvou o seguinte conteúdo Mensagem a ser salva no disco.
  • Esses foram alguns exemplos sobre o que se trata cada tipo de design pattern, mas há vários outros nos tipos citados. Cada um tendo sua aplicação e necessidade a seu uso.

Design patterns não são a fórmula perfeita para resolver tudo!

  • Quando se aprende design patterns um dos problemas é querer aplicá-los em todos os lugares possíveis do projeto, onde na verdade um simples código já resolveria.
  • Então basta saber quando precisar utilizar ou não, problemas como conexões com bancos de dados utilizar o padrão singleton, ou decorator para adicionar funcionalidades a objetos durante tempo de execução.
  • Analisar antes de aplicar, as vezes pode estar utilizando uma bazuca para matar uma barata!

Espero ter ajudado a ter uma pequena introdução ao assunto e bora aprofundar ainda mais e melhorar a qualidade de seus códigos?

Segue um ótimo guia que vai te ensinar a utilizá-los e com exemplos de como separar os módulos e classes, o Refactoring Guru, onde este artigo foi baseado: https://refactoring.guru/

Compartilhe
Recomendados para você
Deal - Spring Boot e Angular (17+)
Cognizant - Arquitetura com Spring Boot e Cloud
Claro - Java com Spring Boot
Comentários (0)
Recomendados para você