- Trabalho apresentado à disciplina de Programação 2 - matéria presente na grade do Curso de Ciência da Computação, como parte dos requisitos necessários para a composição da AB1.
- Professor: Mário Hozano.
- Alunos: João Jacinto e Caio Rocha
- O presente relatório consiste em uma explicação e exemplificação dos padrões de arquitetura de códigos utilizados durante o desenvolvimento do sistema WePayU, como também o destaque dos aspectos positivos/negativos de cada arquitetura levando em consideração as necessidades da aplicação.
- Padrões de Design de Desenvolvimento implementados:
- Padrão "Facade"
- Padrão de "Camadas"
- Padrão "Repository"
- Padrão "Models"
- Padrão "Services"
- Padrões de Design de Desenvolvimento implementados:
O "Facade" é considerado um padrão de projeto/design estrutural que busca abstrair a inicialização/estruturação de objetos que seriam essenciais para o funcionamento da aplicação. Esse padrão sugere a implementação de um encapsulamento de "subsistemas" da aplicação em um objeto único de Interface permitindo a inicialização individual dos objetos e garantindo uma maior facilidade no manuseio de dependências. A classe "Facade" deve redirecionar as chamadas do código principal para os objetos apropriados/específicos do subsistema, além disso, caso o código principal não gerencie o ciclo de vida ou inicialize o subsistema, a classe também fica responsável por essa tarefa. De outro modo, é necessário uma certa cautela para manusear as classes que seguem esse padrão pois, caso a classe "Facade" acople todas as outras classes inerentes à aplicação, ela pode se tornar um "objeto deus", fugindo do propósito inicial sugerido/resolvido pelo padrão "Facade".
-
Vantagens de utilização do padrão "Facade":
- Interfaces mais simplificadas: a disponibilização de uma interface simplificada e única que permite a manipulação de toda a aplicação.
- Desacoplamento: o padrão permite o desacoplamento entre o código do Cliente e o código relacionado ao subsistema, desse modo, o cliente fica isento dos detalhes de implementação do próprio subsistema da aplicação.
- Encapsulamento: Os detalhes internos do subsistema são encapsulados juntamente com a "fachada". O Cliente somente possui o acesso à Interface relacionada à "Fachada".
- Manutenção e Refatoração: facilita o processo de melhorias/refatoração, uma vez que mudanças realizadas no subsistema podem ser realizadas sem afetar diretamente o cliente.
- Boas práticas e promoção de Leitura e Usabilidade: ao promover a utilização de uma interface clara e simples, como também a adoção de nomenclaturas de métodos que refletem as suas reais operações, o padrão facilita o bom manuseio do sistema como um todo.
-
Desvantagens de utilização do padrão "Facade":
- Flexibilidade Limitada: o acesso ao subsistema proporcionado pela Interface "facade" talvez não forneça o acesso necessário para alguns clientes.
- Acoplamento implícito: mudanças na Interface podem, sem aviso, afetar os clientes.
- Redução da Visibilidade da Complexidade do Subsistema: uma vez que a "Facade" abstrai complexidade do sistema, ela pode omitir informações cruciais para o funcionamento do subsistema.
-
Exemplo de implementação:
public class Facade { private final EmpregadosRepository empregadosRepository = new EmpregadosRepository(); private final SistemaFolha sistemaFolha = new SistemaFolha(); private final SistemaVendas sistemaVendas = new SistemaVendas(); private final SistemaEmpregados sistemaEmpregados = new SistemaEmpregados(); private final SistemaTaxaSindical sistemaTaxaSindical = new SistemaTaxaSindical(); public List<String> listaIdMembros = new ArrayList<>(); public void zerarSistema() { empregadosRepository.zeraRepository(); } /** * Salva o estado atual dos empregados. */ public void encerrarSistema() { Utils.salvarEmXML(empregadosRepository.getAllEmpregados(), "./listaEmpregados.xml"); listaIdMembros = new ArrayList<>(); } /** * Remove empregado. * * @param idEmpregado id do empregado. * @throws IdentificacaoMembroNulaException é lançada quando o idEmpregado é nulo. * @throws EmpregadoNaoEncontradoException é lançada quando não é encontrado o empregado. */ public void removerEmpregado(String idEmpregado) throws EmpregadoNaoEncontradoException, IdentificacaoMembroNulaException { var empregado = empregadosRepository.getEmpregadoById(idEmpregado); empregadosRepository.removeEmpregado(empregado); } /** * Captura atributo de um empregado. * * @param idEmpregado id do empregado. * @param atributo atributo do empregado a ser recuperado. * @throws Exception é lançada quando não é possível recuperar o atributo do empregado. */ public String getAtributoEmpregado(String idEmpregado, String atributo) throws Exception { Empregado empregado = empregadosRepository.getEmpregadoById(idEmpregado); return sistemaEmpregados.getAtributoEmpregado(empregado, atributo); }Uma arquitetura em camadas é um estilo arquitetural que busca destacar e dividir as responsabilidades dos segmentos da aplicação, geralmente criando um isolamento e dando um propósito bem específico para cada camada e, desse modo, facilitando a manutenção, evolução e até mesmo o reaproveitamento de código presente no sistema. O padrão para a divisão de camadas foi o seguinte: Modelos, "Repositories" e "Services".
-
Vantagens da utilização dessa arquitetura:
- Padronização do código: a implementação de uma estrutura que é bem definida e segmentada facilita não só a implementação de novas funcionalidades como também corrobora para uma consistência de escrita do código.
- Modularidade: visto que a aplicação é dividida em segmentos com escopos de atuação pré definidos, cada camada pode ser testada de forma independente (testes unitários), além de que a manutenção da aplicação torna-se mais fácil, visto que mudanças em uma camada, necessariamente, não afetam as outras. Outro benefício relacionado a esse contexto seria a implementação de camadas de segurança, uma vez que o acesso aos componentes da aplicação pode ser dividido por interfaces que permitem acesso a apenas uma ou outra camada específica.
O conceito de "Repositories" é um padrão de design de aplicações que busca concentrar/centralizar a manipulação/acesso aos dados de uma aplicação em um segmento de código isolado com a finalidade de encapsular as regras de negócio da aplicação.
-
Vantagens da utilização desse padrão:
- Abstração do acesso aos Dados e Desacoplamento: esse padrão fornece uma camada de abstração sobre as operações de manipulação dos dados visto que a lógica dessas operações não precisam conhecer necessariamente como os dados da aplicação são armazenados.
- Encapsulamento/Reutilização do código: uma vez que a lógica de manipulação dos dados é encapsulada, a manutenção dos "repositories" como também a execução de testes unitários tornam-se mais fáceis/flexíveis de realização.
-
Exemplo de implementação:
public class EmpregadosRepository { List<Empregado> empregados = Utils.carregarEmpregadosDeXML("./listaEmpregados.xml"); DadosEmpregadoSistemaFolha dadosEmpregadoSistemaFolhas = Utils.carregarDadosFolhaDeXML("./listaDadosSistemaFolhas.xml"); DadosEmpregadoSistemaVendas dadosEmpregadoSistemaVendas = Utils.carregarDadosVendasDeXML("./listaDadosSistemaVendas.xml"); DadosEmpregadoSistemaTaxaSindical dadosEmpregadoSistemaTaxaSindical = new DadosEmpregadoSistemaTaxaSindical(); public List<String> atributosEmpregados = new ArrayList<String>(); public List<String> tiposEmpregados = new ArrayList<String>(); public List<String> metodosPagamento = new ArrayList<String>(); public List<Empregado> getAllEmpregados() { return empregados; } public DadosEmpregadoSistemaFolha getAllDadosSistemaFolha() { return dadosEmpregadoSistemaFolhas; } public EmpregadosRepository() { inicializaAtributos(); inicializaTipo(); inicializaMetodoPagamento(); } /** * Adiciona um empregado à lista de empregados. * * @param empregado Empregado a ser adicionado. * @return Lista de empregados após adição. */ public List<Empregado> addEmpregado(Empregado empregado) { empregados.add(empregado); Utils.salvarEmXML(empregados, "./listaEmpregados.xml"); return empregados; } /** * Remove um empregado do repositório. * * @param empregado Empregado a ser removido. */ public void removeEmpregado(Empregado empregado) { empregados.remove(empregado); }O padrão "Models" refere-se à implementação de classes ou interfaces que representam os objetos principais de uma aplicação, geralmente são utilizados para a representação de entidades de negócios.
-
Vantagens da utilização desse padrão:
- Validação dos Dados: os modelos podem incluir uma lógica para validar os dados que estão sendo atribuídos, garantindo uma consistência dos dados.
- Encapsulamento dos Dados: os modelos devem encapsular os seus dados, fornecendo métodos de acesso/modificação, promovendo uma integridade dos dados armazenados.
-
Exemplo de implementação:
public class EmpregadoHorista extends Empregado{ private Double salarioPorHora = 0.0; public EmpregadoHorista(String nome, String endereco, String tipo, MetodoPagamento metodoPagamento, Double salarioPorHora, MembroSindicato sindicalizado) throws Exception { super(nome, endereco, tipo, sindicalizado, metodoPagamento); setSalarioPorHora(validarSalario(salarioPorHora)); } public EmpregadoHorista(){} /** * Obtém o valor do salário por hora do empregado horista. * * @return Valor do salário por hora do empregado horista. */ public double getSalarioPorHora() { return salarioPorHora; } /** * Define o valor do salário por hora do empregado horista, realizando validação. * * @param salarioPorHora Valor do salário por hora a ser atribuído. */ public void setSalarioPorHora(Double salarioPorHora) { this.salarioPorHora = salarioPorHora; } /** * Validação do salário do empregado horista. * * @param salario Valor do salário a ser validado. * @return Valor do salário se válido. * @throws Exception Exceção lançada se o salário for nulo, zero ou negativo. */ public Double validarSalario(Double salario) throws Exception { if (salario.isNaN() || salario == 0) throw new Exception("Salario nao pode ser nulo."); if (salario < 0) throw new Exception("Salario deve ser nao-negativo."); else return salario; } }O padrão "Services" é um conceito que é amplamente utilizado na arquitetura de aplicações, seu principal objetivo é encapsular a lógica de negócios de cada segmento de atuação da aplicação, com a finalidade de tornar o código modular, reutilizável e com a sua responsabilidade específica.
-
Vantagens da utilização desse padrão:
- Separação de Responsabilidades: seguindo esse padrão, cada serviço acaba sendo responsável por uma única funcionalidade específica, mantendo uma organização do código, facilitando a compreensão do fluxo lógico da aplicação e reduzindo o acoplamento entre os componentes da aplicação.
- Modularidade: devido ao encapsulamento das funcionalidades, a reutilização dos serviços em diferentes partes da aplicação torna-se mais fácil, além disso a formalização de testes unitários para cada serviço também torna-se mais simples. Outro aspecto importante relacionado à modularidade seria o impacto positivo quanto a manutenção do código, visto que as alterações seriam feitas em contextos singulares.
-
Exemplo de implementação:
public class SistemaFolha { private final EmpregadosRepository empregadosRepository = new EmpregadosRepository(); DadosEmpregadoSistemaFolha dadosEmpregadoSistemaFolha = empregadosRepository.getAllDadosSistemaFolha(); public SistemaFolha() { } /** * Obtém o total de horas normais trabalhadas por um empregado no intervalo de datas especificado. * * @param idEmpregado Identificador único do empregado. * @param dataInicial Data inicial do intervalo. * @param dataFinal Data final do intervalo. * @return String representando o total de horas normais trabalhadas. * @throws Exception Lançada se ocorrer algum erro durante a operação. */ public String getHorasNormaisTrabalhadas(String idEmpregado, String dataInicial, String dataFinal) throws Exception { // Validar as datas fornecidas if (!validarData(dataFinal, "final")) throw new SistemaFolhaException(Mensagens.dataFinalInvalida); if (!validarData(dataInicial, "inicial")) throw new SistemaFolhaException(Mensagens.dataInicialInvalida); // Filtrar os cartões de ponto do empregado no intervalo de datas var dadosDoEmpregadoEmQuestao = dadosEmpregadoSistemaFolha.getCartoes().stream() .filter(dados -> dados.getIdEmpregado().equals(idEmpregado)) .toList(); // Se não houver dados, retornar 0 horas if (dadosDoEmpregadoEmQuestao.isEmpty()) { return "0"; } // Calcular as horas trabalhadas no intervalo var horas = calcularHorasTrabalhadas(dadosDoEmpregadoEmQuestao, dataInicial, dataFinal); // Formatar e retornar o resultado return formatarSomaHoras(horas); }