3.2. Módulo Padrões de Projeto GoFs Estruturais
Padrões Estruturais
Ao modelar um sistema orientado a objetos, especialmente com múltiplas classes interconectadas, um dos principais desafios está em organizar e estruturar as relações entre objetos e classes, de forma que o sistema permaneça flexível, reutilizável e de fácil manutenção. Dentre eles:
-
Composite: O padrão Composite tem como principal objetivo permitir que objetos sejam organizados em estruturas hierárquicas de árvore, onde objetos individuais e composições desses objetos são tratados de maneira uniforme.
-
Adapter: Em sistemas que precisam integrar bibliotecas externas ou componentes legados, sem comprometer a arquitetura interna. O Adapter converte a interface de uma classe existente para uma interface esperada pelo cliente, promovendo reuso, desacoplamento e isolamento de dependências externas.
Inicialmente, considerou-se a inclusão de estruturas condicionais dentro da classe Notificacao, que verificariam o tipo de alerta e executariam manualmente o envio por cada canal necessário. No entanto, essa abordagem resultaria em um alto acoplamento da classe Notificacao às implementações específicas de envio, dificultando sua manutenção e evolução — especialmente no caso da adição de novos canais no futuro.
Para solucionar esse problema, optou-se pela aplicação do padrão de projeto Composite, por meio da implementação da classe EnvioComposto. Essa classe implementa a interface IEnvio, assim como as implementações concretas EnvioEmail, EnvioSMS e EnvioPush. A diferença é que EnvioComposto agrega múltiplas estratégias de envio em uma estrutura unificada, permitindo que diversas notificações sejam enviadas simultaneamente, de forma transparente para o sistema.
Implementação em código - Composite
public interface IEnvio {
void enviar(Notificacao notificacao);
}
public class EnvioEmail implements IEnvio {
@Override
public void enviar(Notificacao notificacao) {
System.out.println("Enviando Email para: " + notificacao.getDestinatario().getNome());
}
}
public class EnvioSMS implements IEnvio {
@Override
public void enviar(Notificacao notificacao) {
System.out.println("Enviando SMS para: " + notificacao.getDestinatario().getNome());
}
}
public class EnvioPush implements IEnvio {
@Override
public void enviar(Notificacao notificacao) {
System.out.println("Enviando Push para: " + notificacao.getDestinatario().getNome());
}
}
public class EnvioComposite implements IEnvio {
private List<IEnvio> canais = new ArrayList<>();
public void adicionarCanal(IEnvio canal) {
canais.add(canal);
}
@Override
public void enviar(Notificacao notificacao) {
for (IEnvio canal : canais) {
canal.enviar(notificacao);
}
}
}
No decorrer das discussões, decidiu-se inserir lógica condicional no código cliente para lidar com cada tipo específico de sensor, como giroscópio ou acelerômetro. No entanto, essa abordagem introduziria alto acoplamento, aumento da complexidade e dificultaria a escalabilidade do sistema com a adição de novos sensores. Como já exemplificado em sala de aula — no caso dos vários caminhos possíveis entre um local e um destino — preencher o código com estruturas if-else seria uma solução "não elegante" e pouco sustentável.
Para contornar essa limitação, optou-se pela aplicação do padrão de projeto Adapter, por meio da criação da interface ISensorAdapter. Essa interface define os métodos esperados pelo sistema — como getDeltaGrau() e getDeltaTempo() — funcionando como um contrato único para qualquer sensor utilizado no processo de análise.
Cada tipo de sensor real (como o giroscópio e o acelerômetro) é encapsulado em sua respectiva classe adaptadora (GiroscopioAdapter, AcelerometroAdapter), que implementa a interface ISensorAdapter e traduz os dados e comportamentos do sensor original para o formato esperado pelo sistema.
Com isso, o componente TargetMonitoramento passa a trabalhar diretamente com objetos do tipo ISensorAdapter, abstraindo-se dos detalhes de implementação dos sensores concretos. Essa solução garante maior flexibilidade, facilidade de manutenção e prepara o sistema para a integração de novos sensores no futuro — bastando, para isso, implementar um novo adaptador compatível com a interface definida.
Implementação em código - Adapter
import java.time.LocalDateTime;
import java.time.LocalDate;
public interface ISensorAdapter {
float getDeltaGrau();
int getDeltaTempo();
}
// Adaptee Acelerometro
public class Acelerometro {
public float getAceleracaoX() {
return 1.5f;
}
public float getAceleracaoY() {
return 0.8f;
}
public String lerDadosBrutos() {
return "dados acelerometro";
}
public void calibrar() {
System.out.println("Acelerometro calibrado");
}
}
// Adaptee Giroscopio
public class Giroscopio {
public float getAnguloX() {
return 10.5f;
}
public float getAnguloY() {
return 5.2f;
}
public String lerDadosBrutos() {
return "dados giroscopio";
}
public void calibrar() {
System.out.println("Giroscopio calibrado");
}
}
// Classe cliente: Monitoramento
public class Monitoramento {
private Situacao situacao;
private int deltaVariacaoGraus;
private int tempoParadoSegundo;
private LocalDateTime dataHora;
private ISensorAdapter sensor;
private ResultadoAnalise resultado;
public Monitoramento(Situacao situacao, ISensorAdapter sensor) {
this.situacao = situacao;
this.sensor = sensor;
this.deltaVariacaoGraus = Math.round(sensor.getDeltaGrau());
this.tempoParadoSegundo = sensor.getDeltaTempo();
this.dataHora = LocalDateTime.now();
this.resultado = new ResultadoAnalise();
}
public String getResumo() {
return "Situação: " + situacao +
", Graus: " + deltaVariacaoGraus +
", Tempo parado: " + tempoParadoSegundo + "s";
}
public ISensorAdapter getSensor() {
return sensor;
}
public ResultadoAnalise getResultado() {
return resultado;
}
}
Implementações das Classes
As implementações completas das classes mencionadas neste documento podem ser conferidas no repositório oficial do projeto, disponível em:
O diretório src/monitora
contém o código-fonte Java estruturado em pacotes.
Referências
Arquitetura e Desenho de Software - AULA - GOFS ESTRUTURAIS - Profa. Milene Serrano - Material em Slides. SERRANO, Milene. Acesso em: 30 de maio de 2025.
Histórico de Versões
Versão | Commit da Versão | Data | Descrição | Autor(es) | Revisor(es) | Descrição da Revisão | Commit da Revisão |
---|---|---|---|---|---|---|---|
1.0 | Ver Commit | 01/06/2025 | Adição do diagrama composite | Altino Arthur, Márcio Henrique e Daniel de Sousa | Revisor | — | — |
1.1 | Ver Commit | 02/06/2025 | Adição da Implementação composite | Altino Arthur, Márcio Henrique e Daniel de Sousa | Revisor | — | — |
1.2 | Ver Commit | 02/06/2025 | Adição da Implementação Adapter | Altino Arthur, Márcio Henrique e Daniel de Sousa | Revisor | — | — |