quarta-feira, 8 de agosto de 2012

Aula 9 - Resolvendo o Exercício da Média dos Alunos


Dando continuidade à última aula (http://linubr.blogspot.com.br/2012/08/aula-8-arrays-e-encapsulamento.html), trataremos nesse material a resolução, por completo, do exercício da Média dos Alunos. Para resolver esse exercício é importante que você tenha assimilado os conceitos do Encapsulamento e utilização de Arrays no Java.
Modelagem do Sistema
Inicialmente vamos reprisar as definições colocadas na aula 8. Por conveniência do aprendizado vou replicar os requisitos funcionais discutidos.  
1. Criar um sistema que permita o cadastro de uma turma que pode conter no máximo 10 alunos;
2. O sistema permite informar a nota de cada aluno;
3. No final o sistema deve calcular a média da turma.
Vamos tratar cada requisito individualmente, tentando modelar o sistema de forma interativa. Primeiro requisito:
1. Criar um sistema que permita o cadastro de uma turma que pode conter no máximo 10 alunos;
Aqui pode-se identificar duas prováveis classes: Aluno e Turma. Sabe-se ainda que existe uma regra de negócio explícita que define uma turma como um conjunto de no máximo 10 alunos. Por entendimento básico de um sistema desse tipo podemos também inferir que turmas com quantidade negativa de aluno não existe. Talvez também não seja interessante que exista turma sem nenhum aluno, mas por enquanto não temos subsidios suficientes para isso.
Destacados esses detalhes, chegamos ao esboço abaixo.
Veja que a multiplicidade da associação (os números nos extremos da linha que liga as classes) já define que 1 TURMA possui no MÍNIMO 1 e no MÁXIMO 10 alunos. Pelo texto do requisito 1, podemos ainda entender que a quantidade de alunos de uma turma varia. Logo, em algum momento o sistema deverá permitir que o usuário informe essa quantidade. Isso será realizado pelo método definirTamanhoTurma() - que poderia também ser expresso utilizando um diagrama de Casos de Uso. Veja nosso primeiro esboço:
Por enquanto só podemos definir isso. Vamos ao segundo requisito.
2. O sistema permite informar a nota de cada aluno;
Podemos concluir que deve existir uma funcionalidade para definir a nota dos alunos (não diga?). Representando isso no nosso diagrama de Casos de Uso, teremos:
Podemos também considerar que cada Aluno possuirá uma nota. Logo, a classe aluno deve conter um atributo chamado nota para guardar esse valor.
Pelo visto essa minhoca tricolor é inteligente! Quando pensamos em uma nota de um aluno, podemos facilmente deduzir que notas negativas ou superiores a 10 seriam inválidas (supondo que a variação da nota seja de 0 a 10). Esse simples fato já inviabiliza a definição desse atributo como público - pois correríamos o risco de outros objetos definirem equivocadamente o valor desse atributo de forma inconsistente. Precisamos definí-lo como private.
Veja que já definimos também métodos na classe Aluno pelos quais vamos alterar e obter o valor do atributo nota. A proteção desse atributo ocorrerá principalmente no método lancarNota().
Veremos agora o requisito 3.
3. No final o sistema deve calcular a média da turma.
Através dessa simples frase podemos inferir uma série de situações. Fica claro que uma outra funcionalidade do sistema seria a de calcular média das notas dos alunos. Vamos incluir esse item no diagrama de Casos de Uso.
Como só existem esses três requisitos, nosso diagrama de Casos de Uso está completo. Certamente que o modelo visual de Casos de Uso representado acima nos ajuda a compreender as funções fornecidas pelo sistema, todavia se distancia muito da implementação. As boas práticas nos orientam em definir, para cada Caso de Uso, a descrição dos cenários. O conjunto do Diagrama de Casos de Uso com a descrição dos cenários é chamada de Modelo de Casos de Uso. Para finalizarmos esse diagrama, vamos definir os cenários abaixo.
Caso de Uso Definir Tamanho da Turma
Cenário Principal:
1. O coordenador deve informar o tamanho máximo da turma.
2. O sistema deve alocar recursos para trabalhar com essa turma.
Cenários Alternativos:
1.1. Caso o coordenador informe um valor inválido (negativo, zero¹ ou superior a 10), o sistema deverá mostrar uma mensagem de erro e definir um valor default de 10 alunos.
Caso de Uso Lançar Notas
Cenário Principal:
1. O sistema deve percorrer todos os alunos da turma.
2. Para cada aluno, o coordenador deverá informar a nota.
Cenários Alternativos:
1.1. Caso o coordenador informe uma nota inválida, o sistema deverá mostrar uma mensagem de erro. Nesse caso o sistema definirá o valor zero² para a nota do aluno.
Caso de Uso Calcular Média
Cenário Principal:
1. O sistema deve calcular a média das notas da turma³.
Observações:
1- Na descrição do sistema não foi definido se uma turma pode estar vazia, todavia, não faz sentido nesse sistema calcular a média de uma turma sem alunos. Portanto optei em considerar uma turma de zero alunos como inválida.
2- Talvez uma melhor solução para esse caso seria permitir que o coordenador informe novamente a nota inválida. Como ainda não conversamos sobre Exceptions no Java, optei em simplificar essa condição definindo um valor default (no caso, zero) quando a nota informada pelo coordenador for inválida.
3- Não é papel do Modelo de Casos de Uso descrever cálculos. Porém devemos deixar explícito que o sistema realizará um cálculo nessa situação.
Nosso Modelo de Casos de Uso está pronto. Dando continuidade na modelagem.
A UML prega que o Ator que interage com os Casos de Uso é um elemento externa ao sistema. Em alguns casos até vamos representá-lo como uma entidade no Java, mas esse papo é mais para frente! O que importa é o seguinte: cabe ao analista definir junto ao usuário quem deverá interagir com as funcionalidades do sistema. Nossa definição dos requisitos funcionais está muito pobre, então tomei a liberdade de definir um nome de Ator que talvez se aplicaria a um sistema acadêmico real. Fique à vontade para alterar o nome desse Ator conforme sua interpretação. Só tenha cuidado, é claro, para não definir nomes dissonantes da abstração do sistema - ou seja, se você definir um Ator chamado Padeiro ou Astronauta, estará errado! Nomes alternativos como Operador, Funcionário, talvez sejam viáveis também.
Voltando ao Diagrama de Classes, fica claro que precisaremos de um comportamento calcularMedia() na classe Turma. Dentro desse contexto podemos também entender que uma instância de Aluno só pertencerá a uma única Turma. Isso justifica definir uma multiplicidade de mínimo e máximo 1 na associação de Aluno com Turma.
Veja na definição que 1 INSTÂNCIA DE ALUNO pertence a 1 ÚNICA TURMA. Temos algumas considerações muito importantes a respeito desse modelo. Vamos destacá-las em separado.
Você deve estar pensando: Putz... não entendi nada!! Vamos reformular a pergunta: nós vamos precisar programar essas classes, certo?! A pergunta é: Como vamos representar no código a associação entre Turma e Aluno? Dentro da classe Turma nós teremos um array para guardar os alunos ou dentro da classe Aluno nós teremos um atributo Turma que indicará a turma do aluno?
Essa é uma difícil definição que é questionada somente em tempo de projeto (entenda “tempo de projeto” como aquele momento em que os analistas pensam no sistema mais próximo do código fonte). Respondendo essa pergunta estaremos indicando a navegabilidade da associação. Para definir uma boa navegação nas associações deve-se sempre pensar sob o prisma dinâmico do sistema. Tanto o diagrama de Casos de Uso quanto o Diagrama de Classes não representam plenamente essa informação. Vamos tentar modelar juntos um Diagrama de Sequência para enxergarmos a direção da associação.
Podemos definir um fluxo muito próximo ao código para esse sistema da seguinte forma:
1. Uma turma é formada por um conjunto de alunos;
2. A quantidade de alunos controlados pela turma será informada pelo usuário do sistema. Nesse momento, o array deverá ser instanciado na memória. Podemos também já instanciar os objetos Aluno presentes no array;
3. O método lancarNotas() do objeto Turma deve possuir um looping que percorra todo o array permitindo que o usuário informe notas para cada Aluno. Observe que nesse momento, as notas serão informadas aos alunos através do método lancarNota() do objeto Aluno;
4. Por fim o sistema deverá percorrer todos os alunos afim de calcular a média. A nota de cada Aluno será obtida através do método obterNota() de Aluno.
Analisando esse fluxo, podemos gerar um esboço do Diagrama de Sequência conforme mostrado abaixo:
Mesmo que você não esteja familiarizado com esse diagrama, veja que a linha vertical tracejada ligada ao objeto Turma (essa linha é chamada linha Lifeline ou Linha de Vida) envia algumas mensagens ao objeto Aluno. Quando um objeto A envia mensagens à outro objeto B, significa dizer o objeto A chamou um método declarado no objeto B. Ou seja, quando o objeto Turma envia mensagens ao objeto Aluno, dentro do código dos métodos de Turma precisamos chamar métodos de Aluno. Isso só é possível quando existem objetos alunos declarados dentro de Turma.
Perfeito! Já sabemos que Turma contém Aluno’s. Em algum momento o objeto Aluno enviou mensagens para o objeto Turma (veja que a linha tracejada saindo de Aluno, apontando para Turma, é apenas o retorno da chamada realizada por Turma)?
Não! O objeto Aluno não envia mensagens para Turma! Dessa forma podemos então concluir que Aluno não precisar conhecer qual Turma ele pertence.
Transportando todo esse entendimento para o Diagrama de Classes, temos o seguinte resultado:
A seta do lado direito da associação nos mostra que a classe Turma conhece a classe Aluno. Isso foi possível identificar através da análise realizada no Diagrama de Sequência. Essa seta é chamada de navegabilidade. Observe também que colocamos a palavra alunos próximo da classe Aluno. Uma associação é representada no código Java como atributos da classe. Nesse caso, a classe Turma terá um array de objetos Aluno cujo nome será alunos. O sinal de menos antes desse indicador mostra a visibilidade do atributo.
A navegabilidade significa dizer que existe um acoplamento entre Turma e Aluno, onde Turma depende da classe Aluno. Na prática, podemos concluir que essa dependência nos obrigará dar manutenção no código de Turma sempre que houver alteração no código de Aluno. Como não existe a seta do lado esquerdo da associação, Aluno não conhece turma. Logo, caso alterássemos o código fonte de Turma, não precisaríamos recompilar a classe Aluno. Uma observação que será refinada ao longo do tempo é: uma boa modelagem reflete num código com pouco acoplamento. Do ponto de vista Orientado a Objetos, isso é bom!
Lembre-se na aula 8 (http://linubr.blogspot.com.br/2012/08/aula-8-arrays-e-encapsulamento.html) que chegamos a conversar sobre o atributo length do objeto Array. A classe Turma terá um array de alunos. Logo que inicializarmos esse array com o valor definido pelo usuário, o atributo length passará a armazenar o “total de alunos da turma”. Seria errado replicar esse atributo na classe Turma? Não! Todavia teríamos que nos atentar em mantê-lo exatamente igual ao valor do length do array de alunos.
A característica de preservar responsabilidades (atributos ou comportamentos) de uma classe em um de seus atributos (nesse caso o array de alunos é um atributo da classe Turma) é chamada delegação. Neste exemplo a classe Turma está delegando a responsabilidade de armazenar o total de alunos ao objeto array que ela contém.
Implementando o Sistema
Já temos em mãos a modelagem do sistema. Vamos agora transportá-la para a linguagem Java. No último exercício da aula 6 (http://linubr.blogspot.com.br/2012/08/aula-6-resolvendo-o-exercicio-da.html), optamos em iniciar pelas classes do negócio e finalizar na classe do método void main(). Aqui faremos a abordagem contrária: vamos iniciar pela classe Executora, para depois implementar as classes Aluno e Turma. Vale ressaltar que continuaremos utilizando a classe auxiliar Util já discutida anteriormente. Espera-se, ao final da codificação, que o sistema possua os seguintes arquivos:
Vamos à classe Executora.
public class Executora {
  public static void main(String args[]) {
   
    Turma turma = new Turma();
    Util.escrever( "Entre com o tamanho maximo da turma:" );
    int qtdeAlunos = Util.lerNumero();
    turma.definirTamanhoTurma( qtdeAlunos );
    turma.lancarNotas();
    double media = turma.calcularMedia();
    Util.escrever( "A media da turma e " + media );
  }
}
Veja que o comportamento dessa classe é muito simples. Ela permite que o usuário defina a quantidade de alunos da turma. Em seguida ela chama os métodos de negócio definidos na própria classe Turma. No final ela exibe o valor da média das notas.
Deve ficar claro para vocês que ao optarmos em iniciar a programação pela classe Executora, não poderemos compilar o projeto até que todas as classes estejam prontas. Isso ocorre porque a classe Executora utiliza a classe Turma, que por sua vez utiliza a classe Aluno. Como Turma e Aluno ainda não existem, teremos alguns erros de compilação.
Voltemos ao código. Vamos agora criar a classe Aluno.
public class Aluno {
  private int nota;
  public int obterNota() { return nota; }
  public void lancarNota(int valor) {
    if ( valor >= 0 && valor <= 10 ) {
      // Nota válida
      nota = valor;
    } else {
      // Nota inválida
      nota = 0;
      Util.escrever( "Nota invalida!" );
    }
  }
}
Veja que o código dessa classe é muito simples. O método obterNota() apenas retorna o valor do atributo nota. Observe que ele foi codificado em apenas uma linha. O Java aceita isso sem problemas. Se outros programadores ficarem com raiva de você caso codifique assim, aí é outra história...
O método lancarNota() seguiu a definição estipulada pelo Caso de Uso de mesmo nome. Veja que o uso de comentários no código facilitou a compreensão. Bom... tá certo... esses comentários foram idiotas mesmo!!
Por fim, vamos à classe Turma.
public class Turma {
  private Aluno[] alunos;
  public void definirTamanhoTurma(int t) {
    int qtdeAlunos = 10;
    if ( t > 0 && t <= 10 ) {
      qtdeAlunos = t;
    } else {
      Util.escrever( "Tamanho de turma invalido!" );
    }
    alunos = new Aluno[ qtdeAlunos ];
    for (int i = 0; i < alunos.length; i++) {
      alunos[ i ] = new Aluno();
    }
  }
  public void lancarNotas() {
    for (int i = 0; i < alunos.length; i++) {
      Util.escrever( "Informe a nota do " + (i+1) + " aluno:" );
      int nota = Util.lerNumero();
      alunos[ i ].lancarNota( nota );
    }
  }
  public double calcularMedia() {
    double total = 0;
    for (int i = 0; i < alunos.length; i++) {
      total += alunos[ i ].obterNota();
    }
    return total / alunos.length;
  }
}
Essa classe mantém um conjunto de regras mais intenso. Vamos detalhar seus elementos.
Primeiramente observe que, conforme conversamos, o atributo alunos é um array privado. Veja que até o momento ele não foi instanciado na memória. Somente o declaramos por enquanto.
No método definirTamanhoTurma() nós verificamos se o parâmetro informado pelo usuário é válido. Caso não seja, assumimos o valor 10 (como havíamos definido na modelagem). De posse da quantidade de alunos, instanciamos o array na memória. Por fim criamos um objeto Aluno para cada elemento do array.
O método lancarNotas() percorre todo o array de alunos (variando o índice no FOR entre zero e length) permitindo, para cada um deles, que o usuário informe a nota do aluno. A verificação da nota ocorrerá dentro do método lancarNota() da classe Aluno. Nesse momento só precisamos nos preocupar em passar os valores digitados pelo usuário para os objetos do array.
Por fim, no método calcularMedia() declaramos uma variável interna chamada total e inicializamos seu valor com zero. Percorremos todo o array acrescentando em total as notas dos alunos. No último comando desse método retornamos a divisão do total pela quantidade de alunos (length). Veja que o atributo de retorno do método é do tipo double - pois o cálculo da média poderá resultar em números decimais.
Vamos primeiro executar o programa de forma comportada (dentro dos cenários principais dos Casos de Uso).
Vamos agora executar forçando valores inválidos.
Veja que o sistema assumiu as regras que definimos na modelagem.
No entando, ao atravessar a rua, algo inexplicável aconteceu...
Será que as minhocas foram esmagadas pelo perigoso corredor?
Não perca o próximo episódio, onde o poderoso recurso da herança será discutido!!!
Obrigado pela companhia.
Abraços, Guilherme Pontes

Nenhum comentário:

Postar um comentário