[Pesquisar este blog]

quinta-feira, 22 de fevereiro de 2018

POO::Plena-14-Objetos que usam objetos

POO-F-13-Tratamento de exceções POO-P-15-Associações
A Programação Orientada a Objetos (POO) é uma técnica de construção de sistemas onde um conjunto de objetos se inter-relacionam para produzir os resultados desejados. Todo objeto é uma ocorrência de uma classe, combinando dados e operações específicas, de maneira a definir um conjunto particular de responsabilidades.

A construção de sistemas orientados a objetos (OO) é bastante interessante, pois como os objetos têm suas responsabilidades delimitadas, é possível que as classes que os originam sejam utilizados em situações diferentes, ou seja, a OO promove a reusabilidade de código.

Outro aspecto positivo é que a manutenção do código se torna mais simples, pois, dependendo da qualidade do projeto, podem ser feitas modificações apenas nas classes relacionadas a objetos específicos, sem que isso implique em alterações em outras classes, ou seja, nas demais partes do sistema [7].

Divisão de responsabilidades

Existe um importante princípio no projeto de software que é conhecido como coesão (cohesion) ou divisão de responsabilidades (responsability division). Segundo este princípio, um módulo, uma classe ou um componente de software deve ter uma, e apenas uma, responsabilidade.

Tal responsabilidade deve ser claramente definida, de maneira que, num projeto OO, cada classe deva ser escrita, modificada e mantida para apenas um propósito, representando um tipo único de entidade ou de ator.

Quando uma classe possui muitas responsabilidades é usualmente difícil compreender seu papel, o que torna sua manutenção é mais complicada; além disso, será pouco provável seu reuso, devido a combinação peculiar de responsabilidade.

Por outro lado, quando uma classe é bem coesa, isto é, quanto é centrada numa responsabilidade única, somente uma razão específica leva à sua modificação, o que  simplifica e flexibiliza a realização de mudanças no futuro, pois provavelmente tais alterações não criarão impactos em outras entidades do sistema.

Embora possa existir alguma dificuldade em dividir as responsabilidades que surgem num problema, tal trabalho de separar é compensado pelas facilidades em evoluir.

Problema-Exemplo

Uma empresa especializada na instalação de pisos e carpetes precisa, com grande frequência, calcular a área e o perímetro dos espaços onde tais acabamentos serão instalados, pois tais informações possibilitam determinar a lista de materiais necessários, da qual deriva o orçamento e, depois, o pedido e entrega dos itens envolvidos para realização do serviço. Tais espaços, tipicamente salas e quartos, são tipicamente retangulares.

Assim, um programa simples que determina a área de tais espaços é bastante útil para agilizar o cálculo da área do piso a ser instalado e também o perímetro de rodapés. Existem outros detalhes neste serviço, que serão omitidos por serem menos importantes, de maneira que o problema seja reduzido a "como determinar a área e perímetro de uma sala retangular".

O retângulo é uma entidade do problema, o que sugere a existência de uma classe Retangulo. Um programa simples pode ser construído para obter os dados necessários para criar um objeto retângulo e, com este, determinar as informações requeridas de área e perímetro.

Então é necessário modelar uma classe Retangulo, para o cálculo de área e perímetro; e outra para o programa em si, que interage com o usuário. Isto separa as responsabilidades identificadas.

Modelagem da classe Retangulo

Uma sala ou área retangular é um retângulo, formado por quatro lados perpendiculares, onde os lados opostos têm a mesma medida: a medida maior (de dois dos lados opostos) será denominada comprimento e a medida menor (dos outro lados) de largura. Da geometria sabemos que:
  • área = comprimento * largura
  • perímetro = 2 * (comprimento + largura)
A representação mais simples de um retângulo é uma dupla de medidas (comprimento e largura), pois os seus quatro ângulos são sempre 90°. Isto indica que comprimento e largura são atributos (ou campos) desta entidade (da classe Retangulo), cujos valores são números reais (ou de tipo double) e positivos, ou seja, comprimento>0.0 e largura>0.0.

Como tais atributos tem uma restrição (são apenas positivos), mas o tipo double aceita valores positivos e negativos, devem ser privados, evitando seu acesso direto e indiscriminado. Métodos de acesso ou getters podem ser providos para que o valor de comprimento e largura de um retângulo sejam obtidos e conhecidos; assim como métodos de alteração ou setters devem ser inclusos para que seja possível modificar as medidas do retângulo, respeitando as regras dadas.

Como não é possível definir (e portanto existir) um retângulo sem estas duas medidas, o construtor da classe deve exigir dois argumentos reais para suprí-las. Não deve ser incluso um construtor default, pois não parece existir um retângulo de medida padrão.

Métodos adicionais podem calcular a área e o perímetro, sem demandar outros dados, pois apenas as medidas de comprimento e largura são necessárias. Tais métodos podem ser denominados area ou getArea, perimetro ou getPerimetro.

Esta análise resulta numa classe Java que pode ser como segue.
public class Retangulo {
// atributos
   private double comprimento, largura;

   // construtor parametrizado exige dimensões
   public Retangulo (double comprimento, double largura)
         throws RuntimeException {
      setComprimento(comprimento);
      setLargura(largura);
   }

// métodos de ajuste/mutação
   public void setComprimento (double comprimento)
 
         throws RuntimeException {
      // verifica dimensão
      if (comprimento<=0) {
         throw new RuntimeException("Comprimento invalido: "
            + comprimento);
      }
      // dimensão ok
      this.comprimento = comprimento;
   }
   public void setLargura (double largura)
         throws RuntimeException {
      // verifica dimensão
      if (largura<=0) {
         throw new RuntimeException("Largura invalida: " + largura);
      }
      // dimensão ok
      this.largura = largura;
   }

// métodos de acesso/observação
   public double getComprimento () { return comprimento; }
   public double getLargura () { return largura; }

   public double area() { return comprimento*largura; }
   public double perimetro() { return 2*(comprimento+largura); }

// método de acesso "padronizado"
   public String toString() {
      return String.format("Retangulo[C:%.2d x L:%.2d]",
            comprimento, largura);
   }
}

Um objeto do tipo Retangulo pode ser instanciado com:
Retangulo ret1 = new Retangulo(4.20, 2.70);

As medidas podem ser modificadas com os métodos setter:
ret1.setComprimento(4.30);
ret1.setLargura(2.75);

As medidas podem ser obtidas individualmente com os métodos getter:
double c = ret1.getComprimento();
double l = ret1.getLargura();

Uma String que descreve o objeto pode ser obtida com toString():
// uso direto - explícito
String desc = ret1.toString();
// uso indireto - implícito
System.out.println(ret1);

A área e o comprimento podem ser obtidas com:
double area = ret1.area();
double perim = ret1.perimetro();

Caso medidas inválidas sejam fornecidas, na instanciação ou na alteração de uma das medidas do retângulo, será lançada uma exceção do tipo RuntimeException:
// na instanciação
Retangulo ret2 = new Retangulo(-2, 3.5);
|  java.lang.RuntimeException thrown: Comprimento invalido: -2.0
|        at Retangulo.setComprimento (#1:15)
|        at Retangulo.<init> (#1:7)
|        at (#2:1)

// na alteração
ret1.setComprimento(0);
|  java.lang.RuntimeException thrown: Comprimento invalido: 0.0
|        at Retangulo.setComprimento (#1:15)
|        at (#4:1)

Com isso dispomos de uma classe Retangulo, que modela adequadamente as entidades deste tipo e cujas instâncias podem representar convenientemente salas retangulares, provendo os serviços de cálculo da área e do perímetros destas salas.

Modelagem do programa

O programa mais simples que permite resolver o problema de determinar a área e o perímetro de uma sala retangular precisa:
  1. obter as medidas da sala (comprimento e largura), solicitando-as para o usuário;
  2. criar um objeto de tipo Retangulo com as medidas dadas;
  3. obter a área e o perímetro da sala por meio do uso do objeto Retangulo;
  4. exibir os resultados (área e o perímetro da sala).
Uma classe denominada APS (Área e Perímetro da Sala) é um programa Java válido se contiver um método main(String[]), como o que segue, que realiza as ações listadas acima.
import java.util.Scanner;

public class APS {
   public static void main(String[] args) {
      // obtém medidas da sala
      Scanner teclado = new Scanner(System.in);
      System.out.print("Sala--comprimento: ");
      double comprimento = teclado.nextDouble();
      System.out.print("Sala--largura: ");
      double largura = teclado.nextDouble();

      // cria objeto Retangulo
      Retangulo sala = new Retangulo(comprimento, largura);

      // obtém área e perímetro da sala
      double area = sala.area();
      double perimetro = sala.perimetro();

      // exibe resultados
      System.out.println("Sala--area = " + area);     
      System.out.println("Sala--perimetro = " + perimetro);     

      // finalização do programa
      teclado.close();
   }
}

Quando executado, este programa produz resultados como:
>java APS
Sala--comprimento: 4,3
Sala--largura: 2,75
Sala--area = 11.825
Sala--perimetro = 14.1

Considerações finais

Embora simples, esta solução permite o cálculo da área e do perímetros de uma sala retangular. Além disso, devemos observar que:
  • A classe APS realiza a interação com o usuário, ou seja, solicita valores e exibe resultados.
  • A classe APS não tem conhecimento de como os resultados são obtidos (como são calculados), mas utiliza um objeto de outra classe (Retangulo) que é capaz de realizar tais cálculos.
  • A classe APS é como um cliente da classe Retangulo.
  • A classe Retangulo não realiza qualquer interação com usuário (não existe leitura de valores ou exibição de mensagens), possibilitando seu uso por qualquer tipo de programa (de console, GUI ou web).
  • Cada classe tem suas próprias responsabilidades: mudanças na interação com usuário não afetam a classe Retangulo; mudanças na classe Retangulo - desde que não modifiquem sua interface - não afetam seus clientes.
Esta divisão de responsabilidades acaba por criar uma organização dos elementos da aplicação, isto é, das classes componentes deste sistema que é conhecida como organização em camadas, uma arquitetura bastante conhecida e consagrada.

A figura que segue ilustra o modelo de camadas tradicional:


Considerando apenas as camadas não opcionais, temos:
  • Presentation Layer, ou Camada de Apresentação, que é responsável pela interação da aplicação com seus usuários. Esta camada contém a interface da aplicação.
  • Logic Layer, ou Camada de Lógica ou de Regras de Negócio, contém os elementos necessários para a realização das operações e transações requeridas pela aplicação.
  • Persistence Layer, ou Camada de Persistência, tem como responsabilidade prover os serviços de armazenamento e recuperação dos dados da aplicação, envolvendo bancos de dados, sistemas de arquivos e outros elementos presentes em sistemas locais, em rede, centralizados ou em nuvem.
Nosso exemplo é aderente a esta arquitetura, pois:
  • Classe APS constitui a camada de apresentação da aplicação, interagindo com o usuário na obtenção de dados e exibição de resultados.
  • Classe Retangulo faz parte da camada de lógica, pois é responsável pelas operações da aplicação (cálculo da área e do perímetro de salas retangulares).
  • Não existem elementos da camada de persistência, pois nossa aplicação não armazena dados, o que revela uma fragilidade justificável por sua simplicidade.
Um outro aspecto importante deste exemplo é que a utilização de um objeto por parte de um segundo objeto pode indicar uma associação entre tais objetos, ou seja um vínculo ou um relacionamento. Este é o assunto do próximo post!

POO-F-13-Tratamento de exceções POO-P-15-Associações

Referências Bibliográficas

[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.

Nenhum comentário: