Implementação de Sistemas Universidade Federal do Estado do Rio de Janeiro Centro de Ciências Exatas e Tecnologia Escola de Informática Aplicada Curso: Bacharelado em Sistemas de Informação Disciplina: Construção de Sistemas Professor: Luiz Fernando Seibel Grupo: Eduardo Henrique Cavalcante Fontanha de Oliveira Pedro Nuno de Souza Moura Apresentação Tem este trabalho por objetivo funcionar como um guia e um material especificado para os comentários referentes à apresentação sobre Implementação de Sistemas da disciplina de Construção de Sistemas. Em um primeiro momento, percebe-se que, após a etapa de Projeto de Sistemas e utilizando-se a notação UML, a etapa de geração de código é passível de automatização, dado que se têm à disposição os Diagramas de Colaboração e o Diagrama de Classes do Projeto, isto é, o Modelo Conceitual acrescido de métodos nas classes e sentidos nas associações entre estas. Assim sendo, para gerar o código das classes correspondentes à camada de domínio da aplicação, deve-se seguir a seguinte metodologia para converter o DCP (Diagrama de Classes de Projeto) para o código da linguagem Orientada a Objetos utilizada: • Classes As classes do DCP são imediatamente convertidas em classes na linguagem de programação. • Atributos Os atributos das classes são convertidos em atributos privados, salvo casos em que sejam discriminadamente públicos ou protegidos, da respectiva classe na linguagem de programação. Tais atributos sempre corresponderão a variáveis cujo tipo é built-in, ou seja, é alfanumérico. Além disso, devem ser implementados os métodos de alteração e recuperação para esses atributos, que são os métodos getters e setters, a fim de possibilitar o encapsulamento. É importante notar que, a fim de viabilizar o funcionamento do mecanismo de persistência, é fundamental acessar e modificar os atributos somente pelos métodos getters e setters. Assim sendo, qualquer método, mesmo sendo da própria classe, deverá acessar ou alterar tais variáveis através dos métodos supracitados. Isso ocorre porque, quando o mecanismo de persistência for implementado, será necessário ter controle sobre qualquer alteração feita sobre os objetos, para verificar se estão inconsistentes com o banco de dados. Abaixo segue um exemplo de uma classe Java com métodos para consultar (get) e modificar (set) valores dos atributos: class Cliente { private String nome; private Float debito; private Integer idade; public void setNome(String nome) { this.nome = nome; } public void setDebito(Float debito) { this.debito = debito; } public void setIdade(Integer idade) {this.idade = idade; } public String getNome() { return nome; } public Float getDebito() { return debito; } public Integer getIdade() { return idade; } } • Associações As associações são transformadas em variáveis de instância, da mesma forma que os atributos, e terão métodos para alteração e consulta. Contudo, há uma diferença: os atributos geram variáveis cujo tipo é básico (alfanumérico), ao passo que as associações geram tipos que são classes de objetos ou estruturas de dados. Além disso, considerando as diferentes multiplicidades de papel e outras características das associações, haverá algumas distinções a fazer quanto aos métodos associados. Cada associação deverá implementar no mínimo: a) Um método para criar a associação; b) Um método para destruir a associação. Este método não pode ser implementado no caso de associações para 1, porque elas não podem ser destruídas. Associações 0..1 e * exigem a implementação de tal método; e c) Um método para obter (consultar) os elementos associados. Este método poderá ter muitas variantes, dependendo do tipo da associação. Assim sendo, serão vistos e analisados os diversos tipos de associações existentes: • Associação Unidirecional para 1 A associação unidirecional para 1 deve ser armazenada em uma variável de instância na classe de origem da associação e seu tipo deve ser a classe de destino. Assim sendo, uma associação unidirecional de ItemDeEmprestimo para 1 Emprestimo corresponderá a uma variável de instância na classe ItemDeEmprestimo declarada como tipo Emprestimo. Deve-se observar que, como a associação é estritamente para 1, não é possível destruir a associação e, portanto, esse método não deve ser implementado. Além disso, como a associação 1 é obrigatória para o objeto origem, o método construtor da classe deve ter como parâmetro o elemento a ser associado, para que, desde o momento da criação, todas as instâncias da classe na origem da associação estejam consistentes. Figura 1: Exemplo de classe com associação unidirecional para 1 O código em Java na próxima página reflete a implementação da classe ItemDeEmprestimo: class itemDeEmprestimo { private Emprestimo emprestimo; public ItemDeEmprestimo(Emprestimo emprestimo) { this.associaEmprestimo(emprestimo) } public void associaEmprestimo(Emprestimo emprestimo) { This.emprestimo = emprestimo; } } • public Emprestimo getEmprestimo() { return emprestimo; } Associação Unidirecional com Multiplicidade 0..1 Neste caso, o procedimento de geração de código é semelhante ao da associação 0..1, mas é possível destruir a associação. Portanto, deve ser implementado o método correspondente. Além disso, não é necessário passar um objeto como parâmetro para o método construtor, pois a associação 0..1 não é obrigatória Figura 2: Exemplo de classe com associação unidirecional para 0..1 O código em Java abaixo reflete a codificação da Classe Venda: class Venda { private Pagamento pagamento; public Venda() {} public void associaPagamento(Pagamento pagamento) { this.pagamento = pagamento; } public void desassociaPagamento() { This.pagamento = null; } } • public Pagamento getPagamento() { return pagamento; } Associação Unidirecional para * A associação unidirecional para * corresponde à implementação de um conjunto. Trata-se aqui da associação simples, isto é, não-ordenada e nãoqualificada, dado que tais relacionamentos serão abordados posteriormente. A implementação de uma associação desse tipo constitui-se pela implementação de uma variável do tipo Conjunto, tal como expresso na figura 3 e no respectivo código Java da classe Cliente. Figura 3: Exemplo de classe com associação unidirecional para * O seu código Java segue abaixo: class Cliente { private Set emprestimos = new HashSet(); public Cliente () { } public void adicionaEmprestimo(Emprestimo emprestimo) { this.emprestimos.add(emprestimo); } public void removeEmprestimo(Emprestimo emprestimo) { this.emprestimos.remove(emprestimo); } } public Set getEmprestimos () { return Collections.unmodifiableSet(emprestimos); } Por fim, cabe citar que, caso a classe Emprestimo possuísse atributos identificadores, então se poderia codificar métodos que retornasse um empréstimo em particular ou um subconjunto de empréstimos. • Associação Unidirecional com Multiplicidade Ordenada Quando uma associação para * for ordenada, a diferença crucial na implementação é que se deve usar uma lista (ou um Array) como estrutura de dados, e não um conjunto. A figura 4 demonstra um exemplo de associação ordenada e, logo abaixo, segue o código Java classe Videolocadora. Figura 4: Exemplo de classe com associação unidirecional ordenada para * class Videolocadora { private List reservas = new ArrayList(); public Videolocadora() { } public void adicionaReserva(Reserva reserva, int posicao) { this.reservas.add(posicao,reserva); } public void removeReserva(Reserva reserva) { this.reservas.remove(reserva); } public void removeReservaNaPosicao(int posicao) { this.reservas.remove(posicao); } public List getReservas() { return Collections.unmodifiableList(reservas); } } public Reserva getReservaNaPosicao(int posicao) { return (Reserva) reservas.get(posicao); } Ademais, pode-se, ainda, implementer métodos de inserção como addFirst ou addLast. • Associação Unidirecional com Classe de Associação Quando a associação contém uma classe de associação, é necessário implementar a criação e a destruição de instâncias dessa classe de associação cada vez que uma associação é criada e removida. Classes de associação podem existir em associações com qualquer multiplicidade. Contudo, é mais comum serem usadas em relacionamentos do tipo * para *. A implementação desse tipo de associação consiste em um Map, que associa instâncias do destino da associação a instâncias da classe de associação. O código Java abaixo exemplifica uma classe de associação Emprego no relacionamento * para * entre Pessoa e Empresa. class Pessoa { private Map empresas = new HashMap(); public Pessoa() { } public void adicionaEmpresa(Empresa empresa) { This.empresas.put(empresa, new Emprego()); } public void removeEmpresa(Empresa empresa) { this.empresas.removeKey(empresa); } Public void removeEmprego(Emprego emprego) { this.empresas.removeValue(emprego); } public Set getEmpresa() { return empresas.keys(); } public Set getEmpregos() { return empresas.values(); } } • public Emprego getEmpregoNaEmpresa(Empresa empresa) { Return empresas.at(empresa); } Associação Unidirecional com Multiplicidade 1 na Origem Para qualquer um dos tipos de associação unidirecional definidos, deve-se observar se a multiplicidade na origem é 1. Se for o caso, então a destruição da associação só será possível quando o objetivo for também destruir o objeto no destino da associação, pois se associação for destruída, o objeto, que exige estar associado a um elemento, fica inconsistente. • Associação Bidirecional Quando a associação for bidirecional, ela deve ser implementada em ambas as classes (com redundância controlada). Independente de a multiplicidade ser para 1, para 0..1 ou para *, cada lado da associação será implementada de acordo com as regras expostas anteriormente. Abaixo segue o exemplo de uma associação de 1 para *. Figura 5: Exemplo de associação bidirecional de 1 para * O código Java das classes Cliente e Emprestimo seguem abaixo: class Cliente { private Set emprestimos = new HashSet(); public Cliente () {} public void adicionaEmprestimoAux(Emprestimo emprestimo) { emprestimos.add(emprestimo); } public void removeEmprestimoAux(Emprestimo emprestimo) { emprestimos.remove(emprestimo); } public void adicionaEmprestimo(Emprestimo emprestimo) { if (emprestimo.getCliente() != null) { emprestimo.getCliente().removeEmprestimoAux(emprestimo); } } this.adicionaEmprestimoAux(emprestimo); emprestimo.associaClienteAux(this); public void removeEmprestimo(Emprestimo emprestimo) { this.removeEmprestimoAux(emprestimo); emprestimo.destroi(); } } public Set getEmprestimos() { return emprestimos; } class Emprestimo { private Cliente cliente; public Emprestimo(Cliente cliente) { this.associaCliente(cliente); } public void associaClienteAux(Cliente cliente) { this.cliente = cliente; } public void associaCliente(Cliente cliente) { if(this.cliente != null) { this.cliente.removeEmprestimoAux(this); } } } this.associaClienteAux(cliente); cliente.adicionaEmprestimoAux(this); public Cliente getCliente() { return cliente; } Métodos Delegados Métodos delegados e operações de sistema são gerados a partir da observação dos Diagramas de Colaboração. Assim sendo, um método deve ser implementado em uma dada classe sempre que em algum Diagrama de Colaboração uma instância de tal classe receber uma mensagem delegada. Abaixo segue um exemplo: Figura 6: Diagrama de Colaboração A classe Cliente recebe a mensagem delegada emprestaFita(f). Abaixo segue o código Java da Classe Cliente para tal diagrama de colaboração: class Cliente { private Emprestimo emprestimoAberto; } public void emprestaFita(Fita f) { if (this.getEmprestimoAberto() == null) { this.criaNovoEmprestimo(); } this.getEmprestimoAberto().criaNovoItem(f); } Padrões de Implementação O código a ser gerado durante a fase de Implementação do Sistema necessita ser de fácil compreensão, a fim de qualquer outro desenvolvedor possa entendê-lo, e de fácil manutenibilidade, a fim de que se possa realizar a sua manutenção no futuro. Dessa forma, os padrões de implementação definem os critérios de qualidade esperados para o código. Definem, pois, regras e recomendações em geral a serem utilizadas durante o desenvolvimento. Têm como objetivo ajudar os desenvolvedores a produzir artefatos de qualidade e oferecem instrumentos para se avaliar a qualidade. Um possível conteúdo para padrões de implementação é: • Regras para composição de módulos o Nomes e extensões de arquivos • Padrão para especificações (documentação) o Comentários // /* */ o Cabeçalhos de identificação de arquivos o /* Nome: classe.cpp */ o /* Projeto: TDPV */ o /* Versão: */ o /* Autores: */ o /* Descrição: informações gerenciais */ o Histórico de alterações O benefício do padrão só será percebido caso seja adotado por todos os participantes. Algumas regras podem ser asseguradas por intermédio das ferramentas de codificação. Por fim, se faz imperioso: • Apresentar o padrão aos interessados; • Treinar os participantes com o uso do padrão; e • Controlar sua adoção. Por fim, cabe explicitar que indubitavelmente, sem exageros, os padrões são fundamentais para eliminar os personalismos que possam danificar a qualidade dos programas. MVC (Model-View-Controller) A motivação preponderante do modelo MVC é possibilitar que uma mesma aplicação atenda a diferentes tipos de usuários, cada um com a sua própria interface. Figura 7: Ilustração de uma aplicação com diferentes usuários Assim sendo, é uma arquitetura de software que separa a lógica de controle (Controller), a interface do usuário (View) e a lógica da aplicação (Model) em três componentes distintos, de forma que modificações em um componente possa ser realizada com o mínimo de impacto nos demais componentes. Mais especificamente, possui como pilar sustentador a separação da lógica do domínio da aplicação da interface com o usuário, visto que alterações em tais camadas são direcionadas por interesses diferentes e em circunstâncias diferentes. As três camadas componentes de tal paradigma seguem abaixo: • Modelo O modelo contém o núcleo funcional da aplicação. Dessa forma, representa os dados e as regras de negócio que controlam acessos e atualizações a esses dados. Ademais, não possui qualquer informação sobre a interface com o usuário. • Vista Corresponde às classes que representam os elementos na interface com o usuário. Todos os objetos que o usuário pode ver e responder diretamente na tela, tais como: botões, caixas de texto, etc. É esta camada que recebe a entrada de dados e apresenta o resultado. Assim sendo, acessa o dado através do modelo e especifica como aquele dado deve ser apresentado. É, pois, responsável por manter a consistência em sua apresentação quando o modelo muda. • Controle O controle é responsável por intermediar, isto é, traduzir interações com a Vista em ações a serem executadas pelo Modelo. Portanto, torna-se de fácil percepção que seu cerne é selecionar a vista apropriada, isto é, dizer como esta deve se comportar, baseado nas interações do usuário e no resultado das ações do modelo. Há dois tipos de MVC: MVC1 e MVC2. As figuras abaixo ilustram ambos: Figura 8: Ilustração do modelo MVC1 (MVC Model1 ou JSP Model 1) Figura 9: Ilustração do modelo MVC2 (MVC Model 2 ou JSP Model 2) Figura 10: Comparação do modelo MVC1 e o MVC2 Mostra-se, pois, de fácil percepção que a diferença é que, no MVC2, voltado para a web, o modelo não faz atualizações na vista. Alguns benefícios do uso de MVC são: - Reuso de componentes do Modelo; - Facilita o suporte de novos tipos de cliente; - Várias vistas simultâneas do mesmo modelo; - Vistas Sincronizadas; - Facilita manutenção da interface; - Facilita testes; - Separação de papéis de desenvolvedores; - Modularidade eficiente; e - Facilita a expansão. Design Pattern Na engenharia de software, um design pattern é uma solução geral para problemas que ocorrem com certa freqüência. Baseia-se em descrições, ou templates, ou uma série de definições de regras e passos a serem desenvolvidos durante a confecção de um código que pode ser usado em inúmeras diferentes situações. Os Patterns foram criados como um conceito de arquitetura por Christopher Alexander. Em 1987 começaram a fazer experimentos com a idéia de aplicar patterns à programação, e uma apresentação a este respeito foi feita na conferência de OOPSLA daquele ano. Após a publicação do livro Design Patterns: Elements of Reusable Object-Oriented Software, os design patterns ganharam uma grande popularidade. Exemplos de Web Patterns – Abordagem com Struts Em aplicações Web podemos seguir diferentes processos de implementação de código. Ao implementarmos, de fato, percebemos que os design patterns podem ser divididos em algumas tantas classificações. Desenvolvimento Web com Struts aborda a implementação em camadas conhecida como MVC2, que fora mostrada anteriormente. Mais especificamente, neste caso, suas camadas são: 1 – Modelo: será composta de classes Java-Beans (isto é, classes com propriedades e “getters and setters”) e responsável por persistência de objetos. 2 – Visão: será composta de todo código relacionado à exibição de dados na tela para o usuário (JSP’s, formulários); 3 – Controle (Servlet): camada de servlet implementada pelo próprio Struts, que fará a comunicação, através de um XML, entre a visão e o controle; e 4 – Controle (Actions): camada responsável por execução de ações no sistema e validação de dados. Um pattern estrutural que há na criação de modelos, é quando se tratam exceções com instâncias de sessões entrada e saída de dados abertas. Há um problema quando se fala em persistência, pois para realização de transações e bom uso do banco, sempre que uma transação é terminada, com erro ou não, a sessão que a realizou (a conexão com o banco de dados) tem que ser fechada. Podemos ter supostamente uma classe Conexao que cria essa sessão com o banco de dados, e o código abaixo: public Object método(){ Object o = null; Conexao conexão = new Conexão(); //abre uma conexão try{ //operações } catch (Exception e){ } //instruções } finally { conexao.close(); } return o; Este código é uma definição, um design pattern estrutural, de como se deve proceder para a manutenção de operações com um banco de dados, resolvendo a questão da obrigatoriedade de se fechar toda sessão que se inicia. Em aplicações que utilizam o framework Struts, temos 4 formas de realizarmos as validações de entrada no sistema. São elas: - Validação por Action; - Validação por Form através do método validate(); - Validação direta por cada input presente no Form; e - Validação automática. Nas validações pela Action, basta que controlemos os valores que são agregados a cada propriedade das classes Form-Bean, ou dos Forms dinâmicos da aplicação. Um design pattern para esse tipo de situação poderia ser descrito da seguinte forma: public class ExemploAction extends Action{ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){ ExemploForm exemploForm = (ExemploForm) form; if(exemploForm.getExemplo().trim().equals(“”){ //Tratamento para o erro. } //Operações da ação de exemplo. } } return mapping.findForward(“paginaExemplo”); Com isso, o programador sabe que, ao desenvolver uma Action, primeiro ele vai tratar as propriedades do Form-Bean, e que só então, depois de trabalhados os erros de input, ele realizará as operações da dada Action. A validação automática é a mais especial das validações, pois é capaz de gerar validações tanto na aplicação do lado do servidor quanto do lado do cliente (através de funções em JavaScript). Contudo, essas validações são limitadas (simples) e através de arquivos XML. As validações diretas nas propriedades do Form-Bean, e no método validate() (da classe pai ActionForm) são úteis, pois impedem a execução da Action em caso de erro. Porém, elas não se aplicam a regras de negócio mais complexas, em que os testes de validação vão além dos campos de input em uma página. Bibliografia 1. WAZLAWICK, Raul Sidnei. Análise e Projeto de Sistemas de Informação Orientados a Objetos. Rio de Janeiro: Elsevier, 2004. 2. Material didático da professora Renata Araújo, referente à disciplina de Análise e Projeto de Sistemas, no período de 2006/1 do curso de Sistemas de Informação da UNIRIO. Disponível em: www.uniriotec.br/~renata.araujo/APS. 3. Material didático do professor Leonardo Azevedo, referente à disciplina de Projeto e Construção de Sistemas com Ambiente de Programação, no período de 2006/2 do curso de Sistemas de Informação da UNIRIO. Disponível em: http://leogazevedo.googlepages.com/pcsap. 4. Material didático do professor Márcio Barros, referente à disciplina de Desenvolvimento de Software para Web, no período de 2007/1 do curso de Sistemas de Informação da UNIRIO. 5. Notas de aula da disciplina de Análise e Projeto de Sistemas, do curso de Sistemas de Informação da UNIRIO, ministrada pela professora Renata Araújo no período de 2006/1. 6. Notas de aula da disciplina de Projeto e Construção de Sistemas com Ambiente de Programação, do curso de Sistemas de Informação da UNIRIO, ministrada pelo professor Leonardo Azevedo no período de 2006/2. 7. Notas de aula da disciplina de Desenvolvimento de Software para Web, do curso de Sistemas de Informação da UNIRIO, ministrada pelo professor Márcio Barros no período de 2007/1.