Por que eu devo ler este artigo:Esse artigo apresenta uma comparação entre o paradigma de programação estruturado e o orientado a objetos. Para isso, implementaremos um exemplo seguindo estes dois paradigmas e assim destacaremos as vantagens e facilidades que a Orientação a Objetos fornece ao desenvolvedor em termos de leitura, compreensão, manutenção e evolução do código.

Certamente, a programação estruturada é o primeiro paradigma que muitos desenvolvedores se deparam ao iniciar seus estudos. Tal paradigma leva a programas que são conhecidos por estruturarem as suas funcionalidades em sub-rotinas, geralmente levando à criação de sistemas caracterizados por conter grandes blocos de código-fonte ou sub-rotinas sem organização dos dados.

Por outro lado, a programação orientada a objetos sinaliza para uma maneira mais apropriada de realizar a correta divisão de um programa em conceitos (classes), permitindo que cada classe centralize em si todos os dados relativos ao conceito que ela representa, além de reunir todas as funcionalidades que pertencem a ela. Por exemplo, o conceito de pessoa origina a classe Pessoa, e esta contém atributos como nome, idade e CPF, além de todas as operações (métodos) que são realizadas acessando ou modificando o mesmo conceito.

Em muitas instituições de ensino ainda se começa a aprender programação com o estudo de linguagens que não são orientadas a objetos, ou pior, em muitas vezes se utilizam linguagens orientadas a objetos, mas os programas são criados da mesma forma que no paradigma estruturado.

A programação orientada a objetos é conhecida hoje por ser a sucessora do paradigma estruturado, porém não se deve descartar totalmente o conhecimento sobre esse antigo paradigma, pois ainda podemos chegar a situações onde deve-se dar manutenção a programas estruturados, ou ainda alguns determinados problemas de processamento em lote que podem ter uma melhor performance ao adotar o paradigma estruturado, uma vez que a quantidade de chamadas a métodos é reduzida.

De forma geral, os ganhos das linguagens de programação orientadas a objetos são concentrados na legibilidade do código, na facilidade de manutenção e expansão e na programação simplificada, como ocorre com a linguagem Java, que é interpretada, adicionando assim vantagens como ser independente de plataforma e ter uma sintaxe simples e legível, sem implicar negativamente na performance.

Além disso, a programação orientada a objetos é a escolha natural em qualquer projeto de desenvolvimento de software, onde deseja-se modelar as entidades do mundo real manipulando-se informações das mesmas e realizando operações sobre os dados, o que facilita a análise e o projeto de software, em comparação com o paradigma de programação estruturado.

Dito isso, neste artigo serão mostrados vários exemplos de programação estruturada e sua conversão para a programação orientada a objetos, destacando as vantagens da programação orientada a objetos.

Primeiro exemplo (Programação Estruturada)

Para começar, apresentaremos um exemplo onde se deseja calcular o total de um pedido de venda feito em uma loja, que é uma situação corriqueira a ser solucionada por programação. Nesse exemplo, manipularemos dados de clientes, vendedores, produtos e do próprio pedido, incluindo os seus itens.

O objetivo é, a partir de uma série de dados, processar todos os itens de um pedido de venda e escrever um pequeno relatório incluindo o total do pedido. Vamos escrever esse programa na linguagem Java de forma orientada a objetos, mas primeiro apresentaremos uma versão “estruturada”, onde todo o processamento se concentra em um grande bloco de código.

Listagem 1. Aplicação exemplo – versão estruturada (primeira parte)

package Exemplo1;
  
  public class Programa1 {
  
    private static int[] codigoProdutos;
    private static String[] nomeProdutos;
    private static float[] valorProdutos;
    private static int[] estoqueProdutos;
    private static int[] codigoClientes;
    private static String[] nomeClientes;
    private static String[] enderecoClientes;
    private static int[] codigoProdutosVendidos;
    private static int[] qtdeVendidaProdutos;
    private static int[] codigoVendedores;
    private static String[] nomeVendedores;
    private static float[] percentualComissaoVendedores;
    private static int codigoClienteVenda;
    private static int codigoVendedorVenda;

Na Listagem 1 foram apresentadas todas as variáveis do programa, contendo informações importantes sobre os produtos, clientes, o pedido e seus itens, os vendedores e demais dados do pedido, muitas vezes na forma de vetores.

Nas linhas 5, 6, 7 e 8 são apresentados os dados dos produtos existentes na loja,caracterizados pelas variáveis codigoProdutos, nomeProdutos, valorProdutos e estoqueProdutos. Todas essas variáveis são vetores de mesmo tamanho, sendo que os elementos de mesmo índice em cada um desses vetores se referem ao mesmo produto.

O mesmo comportamento ocorre para as variáveis codigoClientes, nomeClientes e enderecoClientes (linhas 9, 10 e 11). Em seguida, os códigos dos produtos vendidos no pedido são mantidos na variável codigoProdutoVendidos (linha 12) e as quantidades de cada item são mantidas na variável qtdeVendidaProdutos (linha 13). Por sua vez, os códigos, nomes e percentuais de comissão de cada vendedor são atribuídos, respectivamente, às variáveis codigoVendedores, nomeVendedores e percentualComissaoVendedores (linhas 14, 15 e 16).

Por fim, o código do cliente que fez a compra é mantido na variável codigoClienteVenda (linha 17) e o código do vendedor que realizou a venda é mantido na variável nomeVendedorVenda (linha 18).

Na Listagem 2 é apresentado o método para popular essas variáveis para a aplicação exemplo, gerando nosso conjunto de dados.

Listagem 2. Aplicação exemplo – versão estruturada (segunda parte).
private static void criarDadosTeste() {
    codigoProdutos = new int[] { 1, 2, 3, 4 };
    nomeProdutos = new String[] { "Mesa", "Cadeira", "Fogão", "Sofá" };
    valorProdutos = new float[] { 500, 150, 1000, 2000 };
    estoqueProdutos = new int[] { 10, 20, 5, 4 };
    codigoClientes = new int[] { 1, 2, 3 };
    nomeClientes = new String[] { "João", "Carlos", "Carina" };
    enderecoClientes = new String[] { "Rua X, 100", "Rua Y, 200", "Rua Z, 400" };
    codigoProdutosVendidos = new int[] { 1, 2, 3 };
    qtdeVendidaProdutos = new int[] { 1, 6, 1 };
    codigoVendedores = new int[] { 1, 2 };
    nomeVendedores = new String[] { "Ademar", "Rosália" };
    percentualComissaoVendedores = new float[] { 0.1f, 0.2f };
    codigoClienteVenda = 2;
    codigoVendedorVenda = 1;
  }

Em aplicações reais, teríamos muito mais dados a serem carregados no sistema e os mesmos seriam obtidos a partir de um banco de dados, porém carregar dados fixos é conveniente para a nossa aplicação exemplo, pois manipular um pequeno número de clientes, produtos e vendedores torna a aplicação exemplo mais didática. Continuando a declaração da classe Programa1, na Listagem 3, é apresentado o método main(), responsável por realizar a venda, deduzindo os itens vendidos do estoque, e exibir um relatório na tela.

Listagem 3. Aplicação exemplo – versão estruturada (terceira parte).

  public static void main(String[] args) {
    System.out.println("Iniciando Venda (1).");
    criarDadosTeste();
    realizarVenda();
    System.out.println("Venda Concluída.");
  }

Observe que na linha 3 são carregados os dados chamando o método criarDadosTeste() e na linha 4 é realizada a venda, deduzindo as quantidades vendidas do estoque e calculando o valor total do pedido pelo método realizarVenda(), que é declarado na Listagem 4, onde a sua primeira parte é mostrada.

Listagem 4. Aplicação exemplo – versão estruturada (primeira parte do método realizarVenda()).

     private static void realizarVenda() {
  
       float totalPedido = 0;
  
       // Remove os produtos vendidos do estoque e calcula o total:
       for (int contProdutoVendido = 0; contProdutoVendido < codigoProdutosVendidos.length; contProdutoVendido++) {
         int codigoProdutoVendido = codigoProdutosVendidos[contProdutoVendido];
         for (int contProduto = 0; contProduto < codigoProdutos.length; contProduto++) {
           int codigoProduto = codigoProdutos[contProduto];
           if (codigoProdutoVendido == codigoProduto) {
             String nomeProduto = nomeProdutos[contProduto];
             float valorProduto = valorProdutos[contProduto];
             int estoqueProduto = estoqueProdutos[contProduto];
             int qtdeItem = qtdeVendidaProdutos[contProduto];
             float totalItem = valorProduto * qtdeItem;
             estoqueProdutos[contProduto] = estoqueProduto - qtdeItem;
             totalPedido = totalPedido + totalItem;
             System.out.println("Item Pedido: Produto:");
             System.out.println("  Nome do produto  = " + nomeProduto);
             System.out.println("  Estoque Anterior = " + estoqueProduto);
             System.out.println("   Quantidade Item = " + qtdeItem);
             System.out.println("             Valor = " + valorProduto);
             System.out.println("        Total Item = " + totalItem);
           }
         }
       }

Nota-se que a lista de produtos vendidos é percorrida (linha 6) e também a lista de produtos disponíveis no sistema (linha 8). Quando um produto vendido é encontrado na lista de produtos disponíveis (linha 10), seus dados são acessados (linhas 11 a 14), obtendo-se o nome do produto (linha 11), o valor do produto (linha 12), o estoque atual do produto (linha 13) e a quantidade vendida (linha 14).

O total do item corrente é calculado na linha 15, multiplicando o valor do produto pela quantidade vendida. Em seguida, a quantidade vendida é removida do estoque (linha 16) e a variável totalPedido é somada ao total do item (linha 17), fazendo com que no final do processamento desse bloco de código se tenha o valor total do pedido, incluindo a criação de um relatório que descreve os detalhes de cada item (linhas 18 a 23).

Dando continuidade à declaração do método realizarVenda(), na Listagem 5 é apresentado o cálculo da comissão do vendedor.

Listagem 5. Aplicação exemplo – versão estruturada (segunda parte do método realizarVenda()).

     // Busca nome do vendedor e calcula a comissão:
     float percentualComissao = 0;
     for (int contVendedor = 0; contVendedor < codigoVendedores.length; contVendedor++) {
       int codigoVendedorLista = codigoVendedores[contVendedor];
       if (codigoVendedorLista == codigoVendedorVenda) {
         percentualComissao = percentualComissaoVendedores[contVendedor];
         String nomeVendedor = nomeVendedores[contVendedor];
         System.out.println("Nome do Vendedor: " + nomeVendedor);
         System.out.println("Percentual de Comissão: " + percentualComissao);
         break;
       }
     }
     float totalComissao = totalPedido * percentualComissao;

Observe que a lista de vendedores é percorrida (linha 3). Ao encontrar o vendedor que realizou o pedido (linha 5), o percentual de comissão e o nome do vendedor são obtidos (linhas 6 e 7). Como resultado, um pequeno relatório é escrito nas linhas 8 e 9. Por fim, o total da comissão é determinado na linha 10, multiplicando o percentual de comissão pelo total do pedido.

Na Listagem 6 é apresentado o código responsável por obter os dados do cliente e o valor total do pedido.

Listagem 6. Aplicação exemplo – versão estruturada (terceira parte do método realizarVenda()).

       // Busca nome do cliente:
       for (int contCliente = 0; contCliente < codigoClientes.length; contCliente++) {
         int codigoClienteLista = codigoClientes[contCliente];
         if (codigoClienteLista == codigoClienteVenda) {
           String nomeCliente = nomeClientes[contCliente];
           String enderecoCliente = enderecoClientes[contCliente];
           System.out.println("Nome do Cliente: " + nomeCliente);
           System.out.println("Endereço do Cliente: " + enderecoCliente);
           break;
         }
       }
  
       // Acessa os dados totais:
       System.out.println("Valor Total do pedido: " + totalPedido);
       System.out.println("Valor Total da Comissão: " + totalComissao);
     }
   }

Repare que a lista de clientes é percorrida na linha 2 usando um laço de repetição, e ao encontrar o cliente que realizou o pedido (linha 4), seus dados são acessados (linhas 5 e 6) e impressos na tela (linhas 7 e 8). Por último, nas linhas 14 e 15, o valor total do pedido e da comissão são impressos.

O resultado da execução desse programa impresso no console é apresentado na Listagem 7.

Listagem 7. Aplicação exemplo – versão estruturada (resultado da execução).

Iniciando Venda (1).
  Item Pedido: Produto:
    Nome do produto  = Mesa
    Estoque Anterior = 10
     Quantidade Item = 1
               Valor = 500.0
          Total Item = 500.0
  Item Pedido: Produto:
    Nome do produto  = Cadeira
    Estoque Anterior = 20
     Quantidade Item = 6
               Valor = 150.0
          Total Item = 900.0
  Item Pedido: Produto:
    Nome do produto  = Fogão
    Estoque Anterior = 5
     Quantidade Item = 1
               Valor = 1000.0
          Total Item = 1000.0
  Nome do Vendedor: Ademar
  Percentual de Comissão: 0.1
  Nome do Cliente: Carlos
  Endereço do Cliente: Rua Y, 200
  Valor Total do pedido: 2400.0
  Valor Total da Comissão: 240.0
  Venda Concluída.

Uma constatação que pode ser feita sobre o exemplo apresentado é que para que determinado cliente seja acessado, é necessário percorrer a lista de clientes até que se encontre o cliente com o código informado. Felizmente existem outras formas de efetuar essa busca, como usando SQL sobre um banco de dados. Porém, nesse exemplo, utilizamos buscas sequenciais para fins didáticos.

Outro fato importante é que mesmo com o código-fonte escrito numa linguagem orientada a objetos, não quer dizer que esse código seja orientado a objetos, pois ele concentra em um único método todo o processamento da lógica de negócios, apresentando uma baixa otimização no acesso aos dados e falta de organização. Por outro lado, poderíamos criar estruturas de dados para cada conceito, como por exemplo, para os conceitos de cliente, vendedor e produto. No entanto, com isso, estaríamos apenas organizando essas variáveis em objetos separados, não acrescentando nenhuma melhoria relevante no sistema, uma vez que as otimizações necessárias são muito mais profundas.

Além disso, se dividíssemos o método realizarVenda() em três partes, sendo cada parte um método distinto, ainda estaríamos tendo os mesmos problemas apresentados. Para realizar a conversão desse exemplo em um código totalmente orientado a objetos, precisamos reescrevê-lo totalmente, designando cada funcionalidade a uma classe distinta e determinando os atributos das classes e suas operações, inclusive criando referências entre os objetos, melhorando a performance.

Sendo assim, a seguir será apresentada a conversão para a forma puramente orientada a objetos do exemplo de programação estruturada analisado anteriormente. A primeira classe, chamada de Cliente é declarada na Listagem 8.

Listagem 8. Aplicação exemplo – versão orientada a objetos (classe Cliente).

   package Exemplo2;
  
   public class Cliente {
      
     private int codigo;
     private String nome;
     private String endereco;
      
     public Cliente(int codigo, String nome, String endereco) {
       this.codigo = codigo;
       this.nome = nome;
       this.endereco = endereco;
     }
  
     public int getCodigo() {
       return codigo;
     }
  
     public void setCodigo(int codigo) {
       this.codigo = codigo;
     }
  
     public String getNome() {
       return nome;
     }
  
     public void setNome(String nome) {
       this.nome = nome;
     }
  
     public String getEndereco() {
       return endereco;
     }
  
     public void setEndereco(String endereco) {
       this.endereco = endereco;
     }
   }

Observe que na classe Cliente são declarados todos os atributos pertinentes ao conceito de cliente, como o código, o nome e o endereço (linhas 5 a 7). Adicionalmente, são declarados todos os métodos assessores desses mesmos atributos.

Dando continuidade ao exemplo orientado a objetos, na Listagem 9 é declarada a classe Vendedor, que segue o mesmo padrão, concentrando os atributos e métodos relativos ao conceito de vendedor.

Listagem 9. Aplicação exemplo – versão orientada a objetos (classe Vendedor).

   package Exemplo2;
  
   public class Vendedor {
      
     private int codigo;
     private String nome;
     private float percentualComissao;
      
     public Vendedor (int codigo, String nome, float percentualComissao) {
       this.codigo = codigo;
       this.nome = nome;
       this.percentualComissao = percentualComissao;
     }
  
     public int getCodigo() {
       return codigo;
     }
  
     public void setCodigo(int codigo) {
       this.codigo = codigo;
     }
  
     public String getNome() {
       return nome;
     }
  
     public void setNome(String nome) {
       this.nome = nome;
     }
  
     public float getPercentualComissao() {
       return percentualComissao;
     }
  
     public void setPercentualComissao(float percentualComissao) {
       this.percentualComissao = percentualComissao;
     }
   }

Na forma puramente orientada a objetos, as funcionalidades são designadas aos conceitos aos quais elas pertencem. Como existe informação suficiente relativa ao conceito de produto, é originada a classe Produto, declarada na Listagem 10, sendo mais uma das classes que representam os conceitos pertencentes à aplicação.

Listagem 10. Aplicação exemplo – versão orientada a objetos (classe Produto).

   package Exemplo2;
   
   public class Produto {
   
     private int codigo;
     private String nome;
     private int estoque;
     private float valor;
  
     public Produto(int codigo, String nome, int estoque, float valor) {
       this.codigo = codigo;
       this.nome = nome;
       this.estoque = estoque;
       this.valor = valor;
     }
  
     public int getCodigo() {
       return codigo;
     }
  
     public void setCodigo(int codigo) {
       this.codigo = codigo;
     }
  
     public String getNome() {
       return nome;
     }
  
     public void setNome(String nome) {
       this.nome = nome;
     }

     public float getValor() {
       return valor;
     }
  
     public void setValor(float valor) {
       this.valor = valor;
     }
      
     public void entradaEstoque(int quantidade) {
       estoque += quantidade;
     }
      
     public void saidaEstoque(int quantidade) {
       estoque -= quantidade;
     }
   }

Além dos getters e setters clássicos, a classe Produto contém outros métodos especializados para manipular o atributo estoque, que são os métodos entradaEstoque() e saidaEstoque() (linhas 41 a 47). Estes possibilitam adicionar ou remover uma certa quantidade de produtos ao estoque, oferecendo uma forma mais intuitiva para manipular a variável relacionada (estoque).

Outra classe muito importante no exemplo puramente orientado a objetos é apresentada na Listagem 11, chamada de ItemVenda. Esta tem a função de representar os produtos que compõem uma venda, indicando a quantidade vendida e contendo uma referência ao produto vendido.

Listagem 11. Aplicação exemplo – versão orientada a objetos (classe ItemVenda).

   package Exemplo2;
  
   public class ItemVenda {
  
     private int quantidade;
     private Produto produto;
  
     public ItemVenda(int quantidade, Produto produto) {
       this.quantidade = quantidade;
       this.produto = produto;
     }
  
     public int getQuantidade() {
       return quantidade;
     }
  
     public void setQuantidade(int quantidade) {
       this.quantidade = quantidade;
     }
  
     public Produto getProduto() {
       return produto;
     }
  
     public void setProduto(Produto produto) {
       this.produto = produto;
     }
      
     public float calcularValorTotalItem(){
       return quantidade * produto.getValor(); 
     }
   }

A classe ItemVenda contém dois atributos: o primeiro armazena a quantidade vendida e o segundo é uma referência para o produto vendido (linhas 5 e 6). Além dos getters e setters, ItemVenda contém o método calcularValorTotalItem(), que multiplica a quantidade pelo valor de venda do produto, retornando o valor total do item.

Ademais, existe um conceito que representa a maior parte das regras de negócio existentes na aplicação, que é o conceito de pedido, implementado na classe Pedido. A primeira parte dessa classe é implementada na Listagem 12.

Listagem 12. Aplicação exemplo – versão orientada a objetos (classe Pedido - primeira parte).

   package Exemplo2;
   
   public class Pedido {
  
      private Cliente cliente;
      private Vendedor vendedor;
      private ItemVenda[] itens;
  
      public Pedido(Cliente cliente, Vendedor vendedor, ItemVenda[] itens) {
            this.cliente = cliente;
            this.vendedor = vendedor;
            this.itens = itens;
      }
  
      public Cliente getCliente() {
            return cliente;
      }
  
      public void setCliente(Cliente cliente) {
            this.cliente = cliente;
      }
  
      public Vendedor getVendedor() {
            return vendedor;
      }
  
      public void setVendedor(Vendedor vendedor) {
            this.vendedor = vendedor;
      }
  
      public ItemVenda[] getItens() {
            return itens;
      }
  
      public void setItens(ItemVenda[] itens) {
            this.itens = itens;
      }

A classe Pedido contém todos os dados relativos ao conceito de pedido, que são cliente, vendedor e seus itens (linhas 5 a 7). Além disso, são declarados todos os getters e setters desses atributos. Continuando a declaração da mesma classe, na Listagem 13 são declarados mais dois métodos.

Listagem 13. Aplicação exemplo – versão orientada a objetos (classe Pedido - segunda parte).

      public float calcularValorTotalPedido() {
            float valorTotal = 0;
            for (ItemVenda item : itens) {
                   valorTotal = valorTotal + item.calcularValorTotalItem();
            }
            return valorTotal;
      }
  
      public float calcularComissaoPedido() {
            return calcularValorTotalPedido() * vendedor.getPercentualComissao();
      }

Como explicitado no código apresentado, o método calcularValorTotalPedido() percorre a lista de itens (linha 3) com uma sintaxe mais limpa que no exemplo estruturado (sem usar variáveis de índices), e para cada item, obtém o valor total do mesmo chamando o método calcularValorTotalItem() (linha 5), disponibilizado na classe ItemPedido.

Outro método importante da classe Pedido é o calcularComissaoPedido() (vide linha 10). Este é responsável por calcular a comissão do pedido, invocando o método calcularValorTotalPedido() e multiplicando o resultado pelo percentual de comissão do vendedor.

Ao analisar as classes apresentadas, nota-se que cada variável está contida no conceito a qual ela pertence, tornando o código muito mais intuitivo, aumentando a sua capacidade de entendimento e manutenção.

Falta agora declarar o método que concentra a maior parte das funcionalidades da aplicação, chamado de realizarVenda(). Este método tem a função de processar todos os itens da venda, realizar a dedução das quantidades do estoque, realizar o cálculo da comissão do vendedor e imprimir os resultados na tela. Veja o código na Listagem 14.

Listagem 14. Aplicação exemplo – versão orientada a objetos (classe Pedido - terceira parte).

      public void realizarVenda() {
  
            // Remove os produtos vendidos do estoque:
            for (ItemVenda itemVenda : getItens()) {
                   Produto produto = itemVenda.getProduto();
                   String nomeProduto = produto.getNome();
                   float valorProduto = produto.getValor();
                   int estoqueProduto = produto.getEstoque();
                   int qtdeItem = itemVenda.getQuantidade();
                   float totalItem = itemVenda.calcularValorTotalItem();
                   produto.saidaEstoque(qtdeItem);
                   System.out.println("Item Pedido: Produto:");
                   System.out.println("  Nome do produto  = " + nomeProduto);
                   System.out.println("  Estoque Anterior = " + estoqueProduto);
                   System.out.println("   Quantidade Item = " + qtdeItem);
                   System.out.println("             Valor = " + valorProduto);
                   System.out.println("        Total Item = " + totalItem);
            }
  
            // Busca nome do vendedor e calcula comissão do vendedor:
            float percentualComissao = getVendedor().getPercentualComissao();
            String nomeVendedor = getVendedor().getNome();
            System.out.println("Nome do Vendedor: " + nomeVendedor);
            System.out.println("Percentual de Comissão: " + percentualComissao);
  
            // Busca nome do cliente:
            String nomeCliente = getCliente().getNome();
            String enderecoCliente = getCliente().getEndereco();
            System.out.println("Nome do Cliente: " + nomeCliente);
            System.out.println("Endereço do Cliente: " + enderecoCliente);
  
            // Acessa os dados totais:
            float totalPedido = calcularValorTotalPedido();
            float totalComissao = calcularComissaoPedido();
            System.out.println("Valor Total do pedido: " + totalPedido);
            System.out.println("Valor Total da Comissão: " + totalComissao);
      }
   }

Agora, com um código que realmente adota a orientação a objetos, o processamento de um pedido se tornou muito mais intuitivo, pois não precisamos mais da lógica de programação complicada que era utilizada anteriormente para manipular os vetores de dados. Na versão orientada a objetos, as regras de negócio foram organizadas de acordo com o conceito a que elas pertencem.

Inicialmente, nas linhas 5 a 11, ocorre a retirada dos produtos vendidos do estoque. Neste trecho de código, é percorrida a lista de itens do pedido (linha 5) e o produto é acessado usando o getter getProduto() (linha 5). Adicionalmente, os dados do produto são acessados nas linhas seguintes e o método calcularValorTotalItem(), da classe ItemVenda, é chamado para calcular o total do item do pedido, sendo a quantidade vendida do item retirada do estoque na linha 11, usando o método saidaEstoque().

Nas linhas 20 a 24 são obtidos e escritos os dados do vendedor e nas linhas 27 a 30 são acessados os dados do cliente do pedido. Por fim, o valor total do pedido é calculado na linha 32, usando o método calcularValorTotalPedido(), e o total da comissão é calculado pelo método calcularValorComissao() na linha 33, finalmente escrevendo a seguir os mesmos na tela, repetindo tudo o que foi feito na Listagem 7.

Comparando a versão estruturada com a versão puramente orientada a objetos, fica claro que a versão orientada a objetos soluciona o mesmo problema de forma muito mais intuitiva e simples, organizando os dados em conceitos representativos que contêm também métodos, os quais são referentes às operações do sistema.

A seguir, considerando o exemplo apresentado como uma parte de um projeto de software, será apresentada uma demanda de manutenção no código-fonte, em que é necessário adicionar novas funcionalidades.

Segundo exemplo (Programação Orientada a Objetos)

O próximo exemplo a ser analisado é dado pela adição de novas funcionalidades ao exemplo anterior, como se estivéssemos fazendo a manutenção de um sistema. A primeira alteração a ser implementada é a possibilidade de suportar clientes especiais, que têm um percentual de desconto a ser aplicado a qualquer compra que realizarem.

A segunda alteração a ser implementada é fazer com que os itens de pedido possam ser vendidos com desconto, ou seja, cada item de pedido deve conter também o percentual de desconto com que o produto foi vendido.

Como verificaremos na solução puramente orientada a objetos, as duas alterações sugeridas são simples, porém, antes disso, vamos ver como elas são implementadas na versão estruturada, como mostra a Listagem 15, onde é apresentada a primeira parte da classe Programa3.

Listagem 15. Aplicação exemplo (segundo exemplo) – versão estruturada (Primeira Parte).

   package Exemplo3;
  
   public class Programa3 {
  
     //código omitido...
  ...
     private static float[] descontoProdutoVendido;
     //código omitido...
  ...
     private static int[] codigoClientesEspeciais;
     private static float[] percentualDescontoClientesEspeciais;
  
     private static void criarDadosTeste() {
       codigoProdutos = new int[] { 1, 2, 3, 4 };
       nomeProdutos = new String[] { "Mesa", "Cadeira", "Fogão", "Sofá" };
       valorProdutos = new float[] { 500, 150, 1000, 2000 };
       estoqueProdutos = new int[] { 10, 20, 5, 4 };
       codigoClientes = new int[] { 1, 2, 3 };
       nomeClientes = new String[] { "João", "Carlos", "Carina" };
       enderecoClientes = new String[] { "Rua X, 100", "Rua Y, 200",
                         "Rua Z, 400" };
       codigoProdutosVendidos = new int[] { 1, 2, 3 };
       qtdeVendidaProdutos = new int[] { 1, 6, 1 };
       descontoProdutoVendido = new float[] { 0.01f, 0.02f, 0.04f };
       codigoVendedores = new int[] { 1, 2 };
       nomeVendedores = new String[] { "Ademar", "Rosália" };
       percentualComissaoVendedores = new float[] { 0.1f, 0.2f };
       codigoClienteVenda = 2;
       codigoVendedorVenda = 1;
  
       codigoClientesEspeciais = new int[] { 2 };
       percentualDescontoClientesEspeciais = new float[]{ 0.01f };
     }
  
     public static void main(String[] args) {
       System.out.println("Iniciando Venda (3).");
       criarDadosTeste();
       realizarVenda();
       System.out.println("Venda Concluida.");
     }
  

Na primeira parte da classe Programa3, é declarado o método criarDadosTeste(), que cria dados de teste para serem utilizados no exemplo, além do método main(), que realiza a venda, carregando antes os dados de teste. Continuando a declaração, a Listagem 16 apresenta o método realizarVenda().

A fim de implementar o desconto por produto vendido e o desconto dado por clientes especiais, são utilizados os vetores nas linhas 14, 20 e 21. Como retomamos ao exemplo do código estruturado, fica mais evidente a sua desvantagem em relação ao código puramente orientado a objetos, pois o que se tem é uma grande poluição visual causada pela falta de organização hierárquica de dados e operações, dificultando a manutenção.

Listagem 16. Aplicação exemplo (segundo exemplo) – versão estruturada (Segunda Parte).

     private static void realizarVenda() {
  
       float totalPedido = 0;
  
       // Remove os produtos vendidos do estoque
       for (int contProdutoVendido = 0; contProdutoVendido < 
             codigoProdutosVendidos.length; contProdutoVendido++) {
         int codigoProdutoVendido = codigoProdutosVendidos[contProdutoVendido];
         for (int contProduto = 0; contProduto < codigoProdutos.length; contProduto++) {
           int codigoProduto = codigoProdutos[contProduto];
           if (codigoProdutoVendido == codigoProduto) {
             String nomeProduto = nomeProdutos[contProduto];
             float valorProduto = valorProdutos[contProduto];
             int estoqueProduto = estoqueProdutos[contProduto];
             int qtdeItem = qtdeVendidaProdutos[contProduto];
             float descontoItem = descontoProdutoVendido[contProduto];
             float totalItem = valorProduto * qtdeItem;
             float desconto = totalItem * descontoItem;
             totalItem = totalItem - desconto;
             estoqueProdutos[contProduto] = estoqueProduto - qtdeItem;
             totalPedido = totalPedido + totalItem;
             System.out.println("Item Pedido: Produto:");
             System.out.println("  Nome do produto   = " + nomeProduto);
             System.out.println("  Estoque Anterior  = " + estoqueProduto);
             System.out.println("   Quantidade Item  = " + qtdeItem);
             System.out.println("Percentual Desconto = " + descontoItem);
             System.out.println("             Valor  = " + valorProduto);
             System.out.println("    Valor Desconto  = " + desconto);
             System.out.println("        Total Item  = " + totalItem);
           }
         }
       }
           
       // verifica se é um cliente especial e aplica desconto
       for (int contCliente = 0; contCliente < 
           codigoClientesEspeciais.length; contCliente++)
       {
         int codigoClienteEspecial = codigoClientesEspeciais[contCliente];
         if (codigoClienteVenda == codigoClienteEspecial)
         {
           float percentualDesconto = percentualDescontoClientesEspeciais[contCliente];
           float totalPedidoAntes = totalPedido;
           float descontoPedido = totalPedidoAntes * percentualDesconto;
           totalPedido = totalPedidoAntes - descontoPedido; 
                         
           System.out.println(" Percentual Desconto Cliente Especial = " + 
                percentualDesconto);
           System.out.println(" Total Pedido Antes do Desconto       = " + 
                totalPedidoAntes);
           System.out.println(" Valor do Desconto                    = " + 
                descontoPedido);
           System.out.println(" Total Pedido Antes do Desconto       = " + 
                totalPedido);
           break;
         }
       }
    

Analisando o código apresentado, verifica-se que nas linhas 19 a 22 é calculado o desconto sobre os itens de venda (produtos), determinando o valor do desconto multiplicando o percentual de desconto pelo valor total do item. Por fim, nas linhas 41 a 44 foi calculado o desconto para clientes especiais, ou seja, se o cliente que efetuou a compra estiver na lista de clientes especiais codigoClientesEspeciais, é dado o devido desconto.

Listagem 17. Aplicação exemplo (segundo exemplo) – versão estruturada (Terceira Parte).
 
       // Busca nome do vendedor e calcula comissão do vendedor:
       float percentualComissao = 0;
       for (int contVendedor = 0; contVendedor < codigoVendedores.length; contVendedor++) {
         int codigoVendedorLista = codigoVendedores[contVendedor];
         if (codigoVendedorLista == codigoVendedorVenda) {
           percentualComissao = percentualComissaoVendedores[contVendedor];
           String nomeVendedor = nomeVendedores[contVendedor];
           System.out.println("Nome do Vendedor: " + nomeVendedor);
           System.out.println("Percentual de Comissão: " + percentualComissao);
           break;
         }
       }
   
       float totalComissao = totalPedido * percentualComissao;
  
       // Busca nome do cliente:
       for (int contCliente = 0; contCliente < codigoClientes.length; contCliente++) {
         int codigoClienteLista = codigoClientes[contCliente];
         if (codigoClienteLista == codigoClienteVenda) {
           String nomeCliente = nomeClientes[contCliente];
           String enderecoCliente = enderecoClientes[contCliente];
           System.out.println("Nome do Cliente: " + nomeCliente);
           System.out.println("Endereço do Cliente: " + enderecoCliente);
           break;
         }
       }
  
       // Acessa a dados totais:
       System.out.println("Valor Total do pedido: " + totalPedido);
       System.out.println("Valor Total da Comissão: " + totalComissao);
     }
   }

A manutenção do código fonte da nossa aplicação de pedidos pode ser facilitada se considerarmos dividir as variáveis e as funcionalidades em conceitos distintos. Se realizarmos a conversão desse exemplo estruturado para a forma puramente orientada a objetos e depois implementarmos as novas funcionalidades, teremos uma manutenção simplificada com alterações pontuais.

Considerando que o código-fonte orientado a objetos será utilizado por uma organização que precisa crescer com o passar do tempo, é importante que sejam empregados outros conceitos da orientação a objetos, como herança, encapsulamento e polimorfismo, os quais maximizam a reusabilidade, eficiência e flexibilidade das implementações.

Portanto, convertendo para orientação a objetos a nossa aplicação estruturada (Listagens 15, 16 e 17), ao realizar a manutenção para adicionar as funcionalidades de clientes especiais e desconto por item de pedido, podemos ainda empregar herança, encapsulamento e polimorfismo para extrair mais proveito dos recursos da orientação a objetos.

Dentre esses, o conceito de encapsulamento já é utilizado na nossa aplicação, como visto nos diversos atributos protected, porém ainda podemos aplicar o conceito de herança sobre as classes Cliente e Vendedor, de forma a criar uma classe Pessoa, mãe de ambas, viabilizando a reutilização de código e levando a uma organização mais intuitiva das funcionalidades dessas classes.

Deste modo, a seguir é apresentada uma nova versão da aplicação exemplo, incorporando as duas novas funcionalidades de clientes especiais e descontos por item vendido, além de aproveitar melhor as relações de herança, encapsulamento e polimorfismo. Assim, facilitam-se muitas tarefas que ocorrerão em decorrência da expansão desse código na organização em que ele seria utilizado.

Listagem 18. Aplicação exemplo (segundo exemplo) – versão orientada a objetos (classe Pessoa).

   package Exemplo5;
  
   public class Pessoa {
  
     private int codigo;
     private String nome;
      
     public Pessoa(int codigo, String nome) {
       this.codigo = codigo;
       this.nome = nome;
     }
  
     public int getCodigo() {
       return codigo;
     }
  
     public void setCodigo(int codigo) {
       this.codigo = codigo;
     }
  
     public String getNome() {
       return nome;
     }
  
     public void setNome(String nome) {
       this.nome = nome;
     }
   }

Como visto na Listagem 18, a classe Pessoa contém os atributos e métodos que são comuns às classes Cliente e Vendedor. De forma a especializar Pessoa, na Listagem 19 é declarada a classe Cliente, herdando assim todos os atributos e métodos desta (com exceção dos privados).

Listagem 19. Aplicação exemplo (segundo exemplo) – versão orientada a objetos (classe Cliente).

   package Exemplo5;
  
   public class Cliente extends Pessoa {
  
     private String endereco;
  
     public Cliente(int codigo, String nome, String endereco) {
       super(codigo, nome);
       this.endereco = endereco;
     }
  
     public String getEndereco() {
       return endereco;
     }
  
     public void setEndereco(String endereco) {
       this.endereco = endereco;
     }
   }

Outra classe importante é a classe Vendedor, declarada na Listagem 20, que também especializa Pessoa. Uma vantagem dessa nova organização é que a classe Vendedor se tornou menor e mais simples, pois herda os métodos da classe Pessoa, aumentando a legibilidade da aplicação.

Listagem 20. Aplicação exemplo (segundo exemplo) – versão orientada a objetos (classe Vendedor).

   package Exemplo5;
  
   public class Vendedor extends Pessoa {
  
     private float percentualComissao;
  
     public Vendedor(int codigo, String nome, float percentualComissao) {
       super(codigo, nome);
       this.percentualComissao = percentualComissao;
     }
  
     public float getPercentualComissao() {
       return percentualComissao;
     }
  
     public void setPercentualComissao(float percentualComissao) {
       this.percentualComissao = percentualComissao;
     }
   }

Neste momento vale lembrar que a implementação de cliente especial na versão estruturada envolvia a criação de um vetor que continha os códigos dos clientes que eram especiais e outro vetor para manter os descontos dos mesmos, o que é uma péssima estratégia de codificação. Na programação orientada a objetos, pode ser aplicada uma solução muito mais elegante para a mesma funcionalidade: criar uma classe filha de Cliente chamada ClienteEspecial que, além de tudo o que um cliente faz, providencia a implementação do desconto de cliente especial.

Dessa forma, aplicamos diretamente o conceito de polimorfismo, pois podemos ter objetos da classe Cliente que são clientes normais e outros que são clientes especiais. Antes, por exemplo, para verificar se um determinado cliente era especial, era necessário percorrer a lista de clientes especiais procurando pelo código do cliente. Agora, para verificar se um cliente é especial, é necessário apenas utilizar o operador instanceof. Na Listagem 21 é apresentada a classe ClienteEspecial, filha de Cliente, sendo também um exemplo de aplicação de herança.

Listagem 21. Aplicação exemplo (segundo exemplo) – Versão orientada a objetos (classe ClienteEspecial).

   package Exemplo5;
  
   public class ClienteEspecial extends Cliente {
  
     private float percentualDescontoClienteEspecial;
  
     public ClienteEspecial(int codigo, String nome, String endereco,
            float percentualDescontoClienteEspecial) {
       super(codigo, nome, endereco);
       this.percentualDescontoClienteEspecial = percentualDescontoClienteEspecial;
     }
  
     public float getPercentualDescontoClienteEspecial() {
       return percentualDescontoClienteEspecial;
     }
  
     public void setPercentualDescontoClienteEspecial
          (float percentualDescontoClienteEspecial) {
       this.percentualDescontoClienteEspecial = percentualDescontoClienteEspecial;
     }
  
     public float calculaDescontoPedido(float totalPedido) {
       return totalPedido * percentualDescontoClienteEspecial;
     }
   }

Apresentada a classe ClienteEspecial, que introduz uma especialização de Cliente, declaramos, na Listagem 22, a classe Produto, que não sofreu nenhuma alteração em comparação com a versão anterior, puramente orientada a objetos (Listagem 10).

Listagem 22. Aplicação exemplo (segundo exemplo) – Versão orientada a objetos (classe Produto).

   package Exemplo5;
  
   public class Produto {
   //restante do código omitido...

A próxima classe a ser apresentada é a classe ItemVenda, introduzida na Listagem 23. Esta classe recebeu a adição do atributo percentualDesconto e novos métodos para calcular o total do item, além de considerar o desconto aplicável sobre os produtos vendidos em sua implementação.

Listagem 23. Aplicação exemplo (segundo exemplo) – Versão orientada a objetos (classe ItemVenda).

   package Exemplo5;
   
   public class ItemVenda {
  
     private int quantidade;
     private Produto produto;
     private float percentualDesconto;
  
     public ItemVenda(int quantidade, Produto produto, float percentualDesconto) {
       this.quantidade = quantidade;
       this.produto = produto;
       this.percentualDesconto = percentualDesconto;
     }
  
     //código omitido...
  ...
  
     public float calcularValorTotalItemSemDesconto() {
       return quantidade * produto.getValor();
     }
      
     public float calcularDescontoItem() {
       float totalItem = calcularValorTotalItemSemDesconto();
       float desconto = totalItem * percentualDesconto / 100;
       return desconto;
     }
      
     public float calcularValorTotalItem() { 
       return calcularValorTotalItemSemDesconto() - calcularDescontoItem();
     }
   }

Outra classe fundamental para a nossa solução orientada a objetos é declarada na Listagem 24 e corresponde à classe Pedido. Pedido contém alterações devido à implementação das novas funcionalidades, de forma a suportar agora clientes especiais e descontos por itens de pedido.

Listagem 24. Aplicação exemplo – Versão orientada a objetos (classe Pedido).

   package Exemplo5;
  
   public class Pedido {
  
     private Cliente cliente;
     private Vendedor vendedor;
     private ItemVenda[] itens;
  
     public Pedido(Cliente cliente, Vendedor vendedor, ItemVenda[] itens) {
       this.cliente = cliente;
       this.vendedor = vendedor;
       this.itens = itens;
     }
     
     // código omitido...
  ...
     public float calcularValorTotalPedido() {
       float valorTotal = 0;
       for (ItemVenda item : itens) {
         valorTotal = valorTotal + item.calcularValorTotalItem();
       }
       return valorTotal;
     }
  
     public float calcularComissaoPedido() {
           
       if (cliente instanceof ClienteEspecial) {
         ClienteEspecial clienteEspecial = (ClienteEspecial) cliente;
         float totalPedido = calcularValorTotalPedido();
         return (totalPedido-clienteEspecial.calculaDescontoPedido(totalPedido)) * 
              vendedor.getPercentualComissao();
       }
       else
         return calcularValorTotalPedido() * vendedor.getPercentualComissao();
     }
  

Note que até a linha 23 não há nenhuma alteração em relação ao exemplo anterior, porém, depois disso, é exibida a nova implementação do método calcularComissaoPedido(), que verifica se o cliente é um cliente especial usando o operador instanceof (linha 27). Se for um cliente especial, temos um cálculo diferenciado (linhas 28 a 30), caso contrário, segue o mesmo cálculo usado anteriormente (linha 33).

Listagem 25. Aplicação exemplo – Versão orientada a objetos (classe Pedido).

     public void realizarVenda() {
  
       // Removee os produtos vendidos do estoque
       for (ItemVenda itemVenda : getItens()) {
         Produto produto = itemVenda.getProduto();
         String nomeProduto = produto.getNome();
         float valorProduto = produto.getValor();
         int estoqueProduto = produto.getEstoque();
         int qtdeItem = itemVenda.getQuantidade();
         float descontoItem = itemVenda.getPercentualDesconto();
         float desconto = itemVenda.calcularDescontoItem();
         float totalItem = itemVenda.calcularValorTotalItem();
         produto.saidaEstoque(qtdeItem);
         System.out.println("Item Pedido: Produto:");
         System.out.println("  Nome do produto   = " + nomeProduto);
         System.out.println("  Estoque Anterior  = " + estoqueProduto);
         System.out.println("   Quantidade Item  = " + qtdeItem);
         System.out.println("Percentual Desconto = " + descontoItem);
         System.out.println("             Valor  = " + valorProduto);
         System.out.println("    Valor Desconto  = " + desconto);
         System.out.println("        Total Item  = " + totalItem);
       }
     
       float totalPedido = calcularValorTotalPedido();
       float descontoPedido = 0;
       if (cliente instanceof ClienteEspecial) {
  
         ClienteEspecial clienteEspecial = (ClienteEspecial) cliente;
         float percentualDesconto = 
              clienteEspecial.getPercentualDescontoClienteEspecial();
         float totalPedidoAntes = totalPedido;
         descontoPedido = clienteEspecial.calculaDescontoPedido(totalPedidoAntes);
         totalPedido = totalPedidoAntes - descontoPedido;
         System.out.println(" Percentual Desconto Cliente Especial = " + 
              percentualDesconto);
         System.out.println(" Total Pedido Antes do Desconto       = " + 
              totalPedidoAntes);
         System.out.println(" Valor do Desconto                    = " + descontoPedido);
         System.out.println(" Total Pedido após o Desconto       = " + totalPedido);
       }
  
       // Busca nome do vendedor e calcula comissão do vendedor:
       float percentualComissao = getVendedor().getPercentualComissao();
       String nomeVendedor = getVendedor().getNome();
       System.out.println("Nome do Vendedor: " + nomeVendedor);
       System.out.println("Percentual de Comissão: " + percentualComissao);
  
       // Busca nome do cliente:
       String nomeCliente = getCliente().getNome();
       String enderecoCliente = getCliente().getEndereco();
       System.out.println("Nome do Cliente: " + nomeCliente);
       System.out.println("Endereço do Cliente: " + enderecoCliente);
  
       // Acessa os dados totais:
       float totalComissao = calcularComissaoPedido();
       System.out.println("Valor Total do pedido: " + totalPedido);
       System.out.println("Valor Total da Comissão: " + totalComissao);
     }
   }

No método realizarVenda(), nas linhas 6 a 13, são calculados os valores dos itens e deduzidas do estoque as quantidades vendidas. Em seguida, nas linhas 15 a 22, é impresso na tela um relatório que inclui os descontos individuais dos produtos. Na linha 24, por sua vez, chama-se o método para calcular o valor total do pedido. Veja a Listagem 25.

Logo após, nas linhas 26 a 40, se o cliente for um cliente especial, são impressos seus dados na tela; teste que é feito através do uso do operador instanceof (linha 26). O objetivo desse bloco de código é calcular o desconto do cliente especial (linha 29), considerando esse valor no cálculo do valor total do pedido (linha 31). O restante do processamento do método realizarVenda() não foi alterado.

O resultado da execução do segundo exemplo estruturado e seu equivalente na forma orientada a objetos é apresentado na Listagem 26.

Listagem 26. Aplicação exemplo (segundo exemplo) – Resultado da execução.
Iniciando Venda (3).
  Item Pedido: Produto:
    Nome do produto   = Mesa
    Estoque Anterior  = 10
     Quantidade Item  = 1
  Percentual Desconto = 0.01
               Valor  = 500.0
      Valor Desconto  = 5.0
          Total Item  = 495.0
  Item Pedido: Produto:
    Nome do produto   = Cadeira
    Estoque Anterior  = 20
     Quantidade Item  = 6
  Percentual Desconto = 0.02
               Valor  = 150.0
      Valor Desconto  = 18.0
          Total Item  = 882.0
  Item Pedido: Produto:
    Nome do produto   = Fogão
    Estoque Anterior  = 5
     Quantidade Item  = 1
  Percentual Desconto = 0.04
               Valor  = 1000.0
      Valor Desconto  = 40.0
          Total Item  = 960.0
   Percentual Desconto Cliente Especial = 0.01
   Total Pedido Antes do Desconto       = 2337.0
   Valor do Desconto                    = 23.369999
   Total Pedido Antes do Desconto       = 2313.63
  Nome do Vendedor: Ademar
  Percentual de Comissão: 0.1
  Nome do Cliente: Carlos
  Endereço do Cliente: Rua Y, 200
  Valor Total do pedido: 2313.63
  Valor Total da Comissão: 231.36299
  Venda Concluída.

Quanto mais antigo o projeto de software, mais ele é parecido com o paradigma de programação estruturada, onde os programas tendem a apresentar aglomeração de comandos, criando grandes blocos de código. A evolução natural de tal paradigma é a programação orientada a objetos, que permite organizar dados e operações na forma de conceitos, ou seja, classes, simplificando os projetos de software em que a orientação a objetos é aplicada.

Entretanto, não é suficiente usar uma linguagem de programação orientada a objetos. Paralelamente, é de vital importância programar orientado a objetos, adotando sempre que possível herança, encapsulamento e polimorfismo, levando assim a uma maior facilidade de compreensão, manutenção e legibilidade do código.

Confira também