terça-feira, 7 de agosto de 2012

Aula 5 - Lapidando o Aprendizado da Abstração

Amigos, cheguei a comentar que o conteúdo dessas aulas está sendo desenvolvido in loco, junto com a turma de estudo Java que fizemos na empresa. Ao iniciarmos essa 5ª aula, estivemos diante das opções de avançar com o conteúdo do Java (vendo, talvez, encapsulamento) ou reforçar o que vimos até o momento. Como duas amigas do grupo (Ludmilla e Glaucia) ainda tinham dúvidas na criação do exercício da Colmeia e um terceiro participante (Walmir) não estava presente, resolvemos aproveitar a aula para concretizar o modelagem e programação de um pequeno sistema Java, ainda sem entrar nas minúcias da linguagem - apenas atacando conceitos de Orientação à Objetos e Abstração.
Daí, o material que trataremos nessa aula será o desenvolvimento de um outro pequeno programa em Java, porém detalhando os passos da modelagem e de como transformá-la em código. Abaixo temos a descrição do sistema.

Descrição do Sistema da Garrafa e do Copo
Deve-se criar um sistema que atenda a situação exposta na figura abaixo:


Hamm? Não entendi!!!

É isso mesmo: vamos modelar um sistema que permita que a água armazenada numa garrafa seja transferida para um copo. Bom... tá certo que não dá para ganhar nenhum Prémio Nobel com o benefício que esse sistema trará, depois de pronto, para a humanidade. Mas lembre-se: nosso objetivo aqui é aprender Java. Inicialmente fica mais fácil de captar o que se propõe na Orientação à Objetos quando programamos situações cotidianas. Por isso, vamos ao sistema da Garrafa e do Copo!

Vamos modelar exatamente a seguinte situação (mundo real):
Um Garçom pega uma garrafa d’água e um copo. Ele enche a garrafa com água. Leva esses itens ao Cliente, que entorna um pouco de água da garrafa no copo.
Observe que quem decide qual o tamanho da garrafa e do copo, tão quanto a quantidade de água que será colocada na garrafa, é o Garçom. Todavia, quem informa a quantidade de água que será transferida da garrafa para o copo é o Cliente.
A principal transação que o sistema fará será a transferência de água da garrafa para o copo. Os outros momentos - definição do tamanho da garrafa, do copo e da quantidade de água presente na garrafa - serão os insumos do sistema.

Transportando essa situação para um modelo descritivo do sistema:
Deve-se modelar e programar um sistema que realize a passagem de água entre dois recipientes, sendo um deles uma garrafa e o outro um copo. Dois usuários terão interação com o sistema: um Garçom e um Cliente (cliente de um restaurante, por exemplo). O sistema deverá permitir que o Garçom informe a capacidade máxima de ambos recipientes. Para a garrafa, o Garçom também informará a quantidade de água atual. O copo, de início, estará vazio. Tendo de posse esse itens, o Cliente deverá informar a quantidade de água que será transferida da garrafa para o copo, sendo que, caso não haja água suficiente na garrafa, será transmitida apenas a quantidade que houver e, caso a quantidade exceda a capacidade máxima do copo, o líquido deverá entornar.

Considerações importantes:
Veja que quem enche a garrafa com água, é o Garçom. O que ocorre caso ele encha a garrafa com uma quantidade de água superior à sua capacidade máxima? Isso mesmo! Parte da água entorna. Isso também é uma regra válida para o copo. Quando o Cliente for transferir a água da garrafa para o copo, caso a quantidade exceda o limite máximo do copo, uma parte da água será entornada. Essas regras deverão estar presentes no sistema.

Nesse exercício também vamos utilizar a classe Util disponibilizada na Aula 3.

Let's go rock!

Analisando o Sistema
Como bons analistas que somos, vamos manter a praxe de se começar um sistema com a modelagem de Casos de Uso.
Quais são as principais funções que o sistema deverá oferecer? Na verdade, será apenas uma, que seria transferir água da garrafa para o copo. Outros itens, como falamos, farão parte do cenário principal de um Caso de Uso chamado Transferir Água.
Quem seriam os Atores desse Caso de Uso?
Justamente: Garçom e Cliente.

Modelo de Casos de Uso


Veja que nessa situação, para que ocorra o cenário disponível no Caso de Uso Transferir Água, dois Atores devem ter interação com o sistema simultaneamente. Isso significa que caso um eles não esteja presente, o sistema não roda! Abaixo descrevemos os cenários.

Cenário Principal:
1- O sistema pergunta ao Garçom a capacidade máxima da garrafa e do copo;
2- O sistema pergunta ao Garçom a quantidade de água que será colocada na garrafa (encher a garrafa);
3- O sistema pergunta ao Cliente a quantidade de água que será repassada da garrafa para o copo;
4- O sistema repassa a água da garrafa para o copo;
5- O sistema informa a quantidade de água atual da garrafa e do copo.

Cenários Alternativos:
2.1- Caso o Garçom informe uma quantidade de água maior que a capacidade da garrafa, o sistema informará que a água excedente foi entornada e deixará a garrafa com sua quantidade de água igual à capacidade máxima.
3.1- Caso o Cliente informe uma quantidade de água maior que a capacidade da copo, o sistema informará que a água excedente foi entornada e deixará o copo com sua quantidade de água igual à capacidade máxima.
3.2- Caso não haja água suficiente na garrafa, o sistema repassará para o copo apenas a quantidade de água atual.

Esse foi nosso passo inicial. Vamos ao Diagrama de Classes.

Quais classes precisamos para atender esse sistema?
Vamos primeiro identificar, através do que foi discutido até o momento, que atributos e comportamentos são pertinentes à garrafa e ao copo.


De cara podemos notar que ambos possuem um atributo chamado Capacidade Máxima que, logicamente, indicará o total de água que cada um deles poderá armazenar.

Obs.: O fato de ser uma garrafa de vodka é méra coincidência... rsrsrs.

Pergunta: Um copo sempre estará cheio até sua capacidade máxima? E uma garrafa? É claro que tanto a garrafa quanto o copo podem conter uma quantidade de água atual inferior à capacidade máxima. Isso nos remete a criação de mais um atributo chamado Quantidade Atual - sendo esse responsável por armazenar a quantidade atual de água que existe nos objetos Garrafa e Copo.
Pronto, à priori já temos nossos atributos.

E os métodos?
É de nosso interesse que tanto o copo, quanto a garrafa, tenham comportamentos de encher - o Garçom enche a garrafa e o Cliente enche o copo. O que esse método fará? Fica explícito que ao encher uma garrafa com água estaremos incrementando o valor do atributo Quantidade Atual, sendo que, caso esse atributo exceda a Capacidade Máxima, um pouco água será entornada. O mesmo pode-se dizer para o comportamento encher de copo.

Como vamos retirar a água da garrafa e repassar para o copo?
Exatamente! Precisamos ainda de um comportamento chamado esvaziar em que o atributo Quantidade Atual é reduzido até alcançar o pedido de transferência do Cliente ou seu limite inferior natural de zero mililitros. Uma característica a ser considerada nesse método seria que, caso precisemos esvaziar uma garrafa com um valor superior à sua Quantidade Atual de água, ela deverá ser esvaziada até que toda sua água termine, mesmo que isso não atenda o pedido do Cliente.
Precisamos esvaziar o copo? Esvaziar também é reconhecido como um comportamento natural de copo, mas à princípio não precisaremos implementá-lo em copo, uma vez que este método não será chamado nesse sistema.

Além dos dois métodos principais, destacados acima, vamos também incluir os métodos mostrarQtdeAtual() (que irá exibir no prompt a quantidade atual de água) e definirCapMax() (para permitir que o Garçom defina o valor do atributo capMax através de um método - por enquanto, entenda isso apenas como uma solução mais elegante).

Diante do discutido, chegamos ao esboço abaixo:


Veja que a única diferença de estrutura entre as classes é o método esvaziar() que não será implementado na classe Copo. Do resto tudo é igual.

Aqui vamos então abordar a famosa técnica de programação, de uso mundial, chamada POG (Programação Orientada à Gambiarra), onde criasse primeiro a classe Garrafa e usa o avançado recurso de Ctrl+C e Ctrl+V para criar a classe Copo!
Pessoal, vocês não estão achando que tem caroço nesse angu?

Gente, peraeee né!!! Do ponto de vista desse sistema, qual a diferença de uma garrafa para um copo?
Ahhhh... já sei... a classe garrafa cabe mais água que uma classe copo!!!
Claro que não malandro! Quando falamos que o OBJETO Garrafa possui, em seu atributo capMax, um valor superior ao do OBJETO Copo, em momento nenhum podemos pensar nas classes. Estamos falando de instâncias diferentes de objetos que possuem valores diferentes para seus atributos. Pense comigo: porque não podemos considerar Garrafa e Copo como sendo instâncias diferentes de uma mesma Classe?

Mas como seria o nome dessa classe então?
Garrafa é um pote? Sim. Podemos chamá-la de Pote. É uma vazilha? Não é o nome mais apropriado, mas, sim, é uma vazilha. É um guardador de água? Sim.
E um copo? Também se enquadra nessas categorias.
Podemos usar então quaisquer um desses nomes, todavia, achei mais elegante se chamarmos essa classe de Recipiente. Que tal?


Dessa forma teremos apenas uma classe Recipiente e a partir dela criaremos duas instâncias diferentes chamadas Garrafa e Copo.
Atenção: na UML a representação de objetos é similar ao de uma classe, porém com as bordas arredondadas. Usei um círculo gráfico para representá-los aqui apenas para facilitar a didática.

Mas e o método esvaziar() do copo? Ele nunca vai usar, certo?
Certo senhores, mas do ponto de vista funcional, não há problema em manter um método esvaziar() para copo - levando em consideração que um copo também pode esvaziar.

Essa seria a melhor solução?
Veja bem! Falar em melhor solução em análise às vezes é complicado. Mas respondendo a sua pergunta: Não, essa não seria a melhor solução! Quando estudarmos Herança poderemos explorar com mais intensidade o reaproveitamento de código, ok? Trataremos de outros exemplos similares a esse no futuro.

Diagrama de Classes
Bom, sendo assim, temos o nosso diagrama de classes.


Trataremos agora do diagrama de sequência.

Elaborando o Diagrama de Sequência
Antes de visualizarmos o próprio diagrama, vou fazer um convite: vamos tentar identificar, sem nenhum empecilho de UML, quais passos nosso sistema fará?
Lembrete: nosso ponto de partida sempre será no método main, que podemos colocar numa classe auxiliar qualquer. Os passos que vamos descobrir aqui são justamente os que ocorrem dentro desse método.

Por conveniência vou replicar aqui a definição do Cenário Principal do Modelo de Casos de Uso.

Cenário Principal:
1- O sistema pergunta ao Garçom a capacidade máxima da garrafa e do copo;
2- O sistema pergunta ao Garçom a quantidade de água que será colocada na garrafa (encher a garrafa);
3- O sistema pergunta ao Cliente a quantidade de água que será repassada da garrafa para o copo;
4- O sistema repassa a água da garrafa para o copo;
5- O sistema informa a quantidade de água atual da garrafa e do copo.

Seguindo essa orientação, podemos detalhar em (numa espécie de português estruturado) os momentos. Separei os números acima em cores que serão representadas pelos passos detalhados abaixo (de mesma cor).

1- Criar uma instância da classe Recipiente chamada garrafa;
2- Ler um número inteiro através do método Util.lerNumero() e guardá-lo numa variável temporária;
3- Chamar o método definirCapMax() do objeto garrafa passando como parâmetro a variável temporária;
4- Criar uma instância da classe Recipiente chamada copo;
5- Ler um número inteiro através do método Util.lerNumero() e guardá-lo numa variável temporária;
6- Chamar o método definirCapMax() do objeto copo passando como parâmetro a variável temporária;

Até esse ponto, resolvemos o item 1 do Cenário Principal. Continuando...

7- Ler um número inteiro através do método Util.lerNumero() e guardá-lo numa variável temporária;
8- Chamar o método encher() do objeto garrafa passando como parâmetro a variável temporária;

Os dois itens acima resolvem o passo 2.

9- Ler um número inteiro através do método Util.lerNumero() e guardá-lo numa variável temporária;
10- Chamar o método esvaziar() do objeto garrafa passando como parâmetro a variável temporária e guardando o valor de retorno dentro de outra variável temporária chamada agua;
11- Chamar o método encher() do objeto copo passando como parâmetro a variável temporária agua;

Esses três passos resolveram os itens 3 e 4 do Cenário Principal. Por fim:

12- Chamar o método mostrarQtdeAtual() do objeto garrafa;
13- Chamar o método mostrarQtdeAtual() do objeto copo.

Pronto. Esses são os passos que vamos descrever no Diagrama de Sequência, porém destacados de forma textual. A grande vantagem do Diagrama de Sequência é mostrar ainda, em detalhes, como as mensagens são trocadas entre os objetos.

Diagrama de Sequência


Você pode comparar os passos detalhados com as mensagens através da identificação dos números (ou seja, o passo de número 6 que detalhamos é equivalente à mensagem de número 6 do Diagrama de Sequência).

Nota ao leitor sedento por conhecimento!
É minha missão passar o conteúdo para vocês de uma forma e velocidade que possa contribuir para a assimilação do conhecimento. Todavia, em alguns pontos abro mão de boas práticas, detalhes de codificação e modelagem para evitar tocar em assuntos ainda não abordados.
Nessa aula, perceba que modelamos um Caso de Uso chamado Transferir Água cujo não foi percebida correspondência explícita no Diagrama de Classes ou mesmo na codificação. Boas práticas sugerem que essas funcionalidades dos Casos de Uso fiquem disponíveis de forma conclusiva - como um método, por exemplo, com o nome transferirAgua(). No último exercício da Colmeia, tivemos a oportunidade de criar no modelo um método de nome equivalente ao explorado no Caso de Uso. Nesse exercício, porém, não fizemos isso.
Porque?
Esse método de “Transferir Água”, talvez seria melhor empregado numa classe que chamamos de Classe de Negócio (também conhecida pelos nomes: Control, Business, Service, etc). Como ainda estamos um pouco distantes desses padrões de projeto, preferi deixar esse comportamento dentro do próprio método main.
Fica aqui então a ressalva de que, caso estivéssemos realizando esse exercício num momento mais adiante no curso, provavelmente criaríamos uma classe de negócio chamada RecipienteBo - ou ainda TransferirAguaService, ou TransferirAguaControl, entre outros possíveis nomes.
Mas não se preocupe amigo! Chegaremos lá e teremos novas oportunidades de trabalhar com classes de negócio.

Atenção: agora é com você!
É muito importante que, antes de ler a próxima aula, você tente codificar sozinho esse sistema! Não tem problema se você errar ou ter que “colar” o formato básico das classes dos outros trabalhos. O importante é que você tente programá-lo e executá-lo no Java.

Na próxima aula faremos, detalhadamente, os passos para a codificação dos diagramas aqui discutidos.
Então é isso... Boa sorte!

Obrigado pela leitura.
Abraços, Guilherme Pontes

Nenhum comentário:

Postar um comentário