Orientação a Objetos – Criação de Classes

Programação 2 – Aulas 6, 7, 8 e 9

Objetivos da seção

Aprender a pensar em testes antes de definir novas classes

Aprender a definir novas classes

     Entender conceitos de atributos, construtores, métodos, parâmetros e valor de retorno, encapsulamento, métodos accessor e mutator, this, métodos-função e métodos-procedimento, aninhamento de métodos, this(), escopo de atributos e variáveis locais, sobrecarga de métodos, métodos de classe, atributos de classe, escopo de atributos de classe, constantes

Apresentar Test-Driven Development (TDD)

A primeira classe

A primeira classe que escreveremos é uma ContaSimples, que já usamos anteriormente

     Na realidade, vamos fazer uma versão um pouco diferente, e é por isso que se chama ContaSimples1

A documentação da classe

A documentação da classe que queremos escrever está aqui

Os testes

Vamos adotar uma postura chamada "Test-Driven Development" (TDD) e escrever os testes antes de escrever o código da classe

     Antes?!?!?!?

     Sim, antes, não depois!

Vamos supor que a classe esteja pronta (foi escrita por alguém) e que queremos testá-la

     Como faríamos?

Eis alguns testes que podemos fazer, escritos em português

Cria um conta com nome "Jacques Sauve", cpf 123456789-01, e número 123

Verifique que o nome da conta é Jacques Sauve

Verifique que o cpf da conta é 123456789-01

Verifique que o número da conta é 123

Verifique que o saldo da conta é 0.0

Deposite R$100,00

Verifique que o saldo é R$100,00

Saque R$45,00

Verifique que o saldo é R$55,00

Tente sacar R$56,00 e verifique que não é possível

Verifique que o saldo continua em R$55,00

Veremos adiante que estes testes não estão completos, mas vamos começar com eles

     Você pode pensar em mais testes que deveriam ser feitos?

Queremos agora fazer com que os testes sejam realizados de forma automática

     Ter testes automáticos é muito, muito bom

     Permite que você execute os testes a qualquer momento

i)    Você pode repetir os testes centenas de vezes sem custo adicional

ii)Imagine a situação se os testes fossem "manuais"

     Permite saber exatamente quando a implementação está terminada (quando os testes rodam)

     Permite fazer alterações ao código ao longo do tempo e assegurar-se de que nada quebrou

     Os próprios testes servem de documentação para a classe

i)    Se você quiser saber como uma classe funciona ou como pode/deve ser usada, examine os testes

Vamos automatizar os testes usando um testador chamado JUnit

     JUnit ajuda a fazer "testes de unidade" (uma unidade = uma classe)

     Tem outros tipos de testes que veremos em outro momento

Vejamos uma classe de testes

package p2.exemplos.tests;

 

import org.junit.Assert;

import org.junit.Before;

import org.junit.Test;

 

import p2.exemplos.ContaSimples1;

 

public class TestaContaSimples1 {

  

   private ContaSimples1 umaConta;

  

   @Before public void criaConta() {

      umaConta = new ContaSimples1("Jacques Sauve", "123456789-01", 123);

   }

 

   @Test public void TestContaSimples1() {

      Assert.assertEquals("Nome errado", "Jacques Sauve", umaConta.getNome());

      Assert.assertEquals("cpf errado", "123456789-01", umaConta.getCPF());

      Assert.assertEquals("Número errado", 123, umaConta.getNúmero());

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

      umaConta.depositar(100.0);

      Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005);

      Assert.assertTrue("Nao consegui sacar", umaConta.sacar(45.0));

      Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005);

      Assert.assertFalse("Consegui sacar demais", umaConta.sacar(56.0));

      Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005);

   }

}

assertEquals, assertTrue, assertFalse são métodos do pacote JUnit e servem para realizar testes

     assertEquals("Mensagem de erro se o teste falhar", string esperado, string a testar)

     assertEquals("Mensagem de erro se o teste falhar", valor double esperado, valor double a testar, precisão)

     assertTrue("Mensagem de erro se o teste falhar", valor a testar que deve retornar true)

     assertFalse("Mensagem de erro se o teste falhar", valor a testar que deve retornar false)

Examine os testes com muita atenção antes de continuar

Tente rodar os testes com JUnit e a classe ContaSimples1.java pronta

Os testes devem rodar (veja figura abaixo)

Introduza erros nos testes e veja o que ocorre

O programa

Vamos fazer de conta que a classe ainda não existe (remova-a!) e precisa ser escrita do zero

Precisamos fazer os testes passarem

     Havendo Eclipse disponível na hora da aula, o professor pode construir a classe aos poucos para fazer os testes passarem, um teste de cada vez

package p2.exemplos;

 

/**

 * Classe de conta bancária simples.

 *

 * @author Jacques Sauvé

 * @version 1.0

 */

public class ContaSimples1 {

   private String nome;

 

   private String cpf;

 

   private int numero;

 

   private double saldo;

 

   // construtor

   /**

    * Cria uma conta a partir de um nome e cpf de pessoa física, e um número

    * de conta.

    *

    * @param nome

    *            O nome do titular da conta.

    * @param cpf

    *            O CPF do titular da conta.

    * @param número

    *            O número da conta.

    */

   public ContaSimples1(String nome, String cpf, int numero) {

      this.nome = nome;

      this.cpf = cpf;

      this.numero = numero;

      saldo = 0.0;

   }

 

   // métodos

 

   /**

    * Recupera o nome do titular da conta.

    *

    * @return O nome do titular da conta.

    */

   public Object getNome() {

      return nome;

   }

 

   /**

    * Recupera o CPF do titular da conta.

    *

    * @return O CPF do titular da conta.

    */

   public Object getCPF() {

      return cpf;

   }

 

   /**

    * Recupera o número da conta.

    *

    * @return O número da conta.

    */

   public Object getNúmero() {

      return numero;

   }

 

   /**

    * Recupera o saldo da conta.

    *

    * @return O saldo da conta.

    */

   public double getSaldo() {

      return saldo;

   }

 

   /**

    * Efetua um depósito numa conta.

    *

    * @param valor

    *            O valor a depositar.

    */

   public void depositar(double valor) {

      saldo += valor;

   }

 

   /**

    * Efetua sacada na conta.

    *

    * @param valor

    *            O valor a sacar.

    * @return O sucesso ou não da operação.

    */

   public boolean sacar(double valor) {

      if (saldo >= valor) {

          saldo -= valor;

         return true;

      }

      return false;

   }

 

   /**

    * Transforma os dados da conta em um String.

    *

    * @return O string com os dados da conta.

    */

   public String toString() {

      return "numero " + numero + ", nome " + nome + ", saldo " + saldo;

   }

 

}

 

Os comentários Javadoc

Há vários comentários iniciando com /**, por exemplo:

/**
 * Classe de conta bancária simples.
 *
 * @author   Jacques Philippe Sauvé, jacques@dsc.ufpb.br
 * @version 1.0
 * <br>
 * Copyright (C) 1999 Universidade Federal de Campina Grande.
 */

Esses comentários servem para criar documentação automática do seu programa através de uma ferramenta chamada javadoc

     Exemplo: ao rodar o seguinte comando: javadoc -d docContaSimples1 -version -author ContaSimples1.java

A saída é esta

Observe como os tags (@author, @version, @param, @return) saíram na documentação

 

A declaração da classe

public class ContaSimples1 {

Novas classes são definida com:

public class ContaSimples1 {

    corpo da classe

}

Atributos

private String nome;

private String cpf;

private int numero;

private double saldo;

Atributos são definidos depois do primeiro { e fora de qualquer corpo de método

"Método" significa função ou subrotina ou sub-programa

Normalmente, atributos são colocados no início da definição da classe, ou talvez bem no final, antes do } final

Os atributos de uma classe são equivalentes aos campos de um "record" ou "struct" em outras linguagens

A diferença básica é que com OO, a classe conterá também código para manipular esses dados

O adjetivo de visibilidade "private" significa que o atributo só pode ser "visto" (usado) pelo código dentro da classe

"public" significa que todo mundo "vê", mesmo fora do corpo da classe

Falaremos mais sobre visibilidade adiante

Os atributos possuem um valor diferente para cada objeto instanciado

Cada ContaSimples1 tem um valor diferente (armazenado em lugar diferente da memória) para o nome de titular, CPF, número de conta e saldo

 

O construtor

/**

    * Cria uma conta a partir de um nome e cpf de pessoa física, e um

    * número de conta.

    *

    * @param nome

    *            O nome do titular da conta.

    * @param cpf

    *            O CPF do titular da conta.

    * @param numero

    *            O número da conta.

    */

   public ContaSimples1(String nome, String cpf, int numero) {

      this.nome = nome;

      this.cpf = cpf;

      this.numero = numero;

      saldo = 0.0;

   }

Ao chamar "new ContaSimples(...)", o método construtor da classe é chamado

     Como qualquer método, pode ter parâmetros (aqui tem 3)

     Porém, o construtor nunca retorna um valor com "return"

O construtor é normalmente usado para inicializar atributos

     this é uma referência especial a este objeto

     Portanto, this.nome se refere ao atributo "nome" do presente objeto ContaSimples1

Se o parâmetro nome se chamasse outra coisa, digamos nomeTitular, a linha poderia ser mudada para:

nome = nomeTitular;

Observe que, aqui, nome referencia o atributo sem precisar usar this

 

Métodos accessor

   // métodos

 

   /**

    * Recupera o nome do titular da conta.

    *

    * @return O nome do titular da conta.

    */

   public Object getNome() {

      return nome;

   }

 

   /**

    * Recupera o CPF do titular da conta.

    *

    * @return O CPF do titular da conta.

    */

   public Object getCPF() {

      return cpf;

   }

 

   /**

    * Recupera o número da conta.

    *

    * @return O número da conta.

    */

   public Object getNúmero() {

      return numero;

   }

 

   /**

    * Recupera o saldo da conta.

    *

    * @return O saldo da conta.

    */

   public double getSaldo() {

      return saldo;

   }

Estamos vendo 4 métodos acima

Observe como um método retorna um valor

     "return expressão" automaticamente pára a execução do método e retorna o valor da expressão para o chamador do método

Como todos esses métodos fazem apenas retornar o valor de um atributo, eles são chamados "accessor methods"

 

Métodos de comportamento

   /**

    * Efetua um depósito numa conta.

    *

    * @param valor

    *            O valor a depositar.

    */

   public void depositar(double valor) {

      saldo += valor;

   }

 

   /**

    * Efetua sacada na conta.

    *

    * @param valor

    *            O valor a sacar.

    * @return O sucesso ou não da operação.

    */

   public boolean sacar(double valor) {

      if (saldo >= valor) {

         saldo -= valor;

         return true;

      }

      return false;

   }

Os dois métodos acima mostram idéias importantes

     Primeiro, a passagem de parâmetros (nos dois métodos)

     Segundo, o método que não retorna nada (indicando com tipo de retorno void)

     Terceiro, o fato de que o saldo da conta não é mexido "de fora"

Quem sabe mexer com o saldo é a ContaSimples1, em si

     Quem usa o objeto "de fora", não tem acesso direto aos atributos do objeto

     Só tem acesso aos métodos que definem o "comportamento" de objetos dessa classe

i)    Neste caso, uma ContaSimples1 deixa que se façam depósitos e saques na Conta

Esses métodos, juntamente com os outros métodos declarados como public, definem a interface da classe

O método toString

   /**

    * Transforma os dados da conta em um String.

    *

    * @return O string com os dados da conta.

    */

   public String toString() {

      return "numero " + numero + ", nome " + nome + ", saldo " + saldo;

   }

Em Java, todo objeto deve ter uma representação como String

     Facilita a impressão com System.out.println()

     Facilita a depuração

Definimos no método toString() o String a retornar para representar o objeto como String

Normalmente, imprimem-se todos os atributos do objeto, em algum formato

 

Documentação de classe com UML

Além de Javadoc, uma outra forma de mostrar o que a classe faz é de usar uma representação gráfica numa linguagem chamada Unified Modeling Language (UML)

Também podemos mostrar apenas a parte pública, sem revelar detalhes internos que não interessam aos clientes da classe

UML é também chamada de linguagem de "modelagem visual"

     Um modelo é uma representação do mundo real que nos interessa

     UML permite criar modelos visuais

     Um programa é um modelo mais elaborado que consegue executar

Usando a classe que acabamos de definir

O fato de definir uma classe como ContaSimples1 não significa que haja objetos desta classe em existência: alguém precisa fazer  new ContaSimples1()

Seguem dois exemplos de programas que usam a classe ContaSimples1

O primeiro exemplo está a seguir:

package p2.exemplos;

 

/*

 * Movimentação simples de uma conta bancária

 */

 

// Programa Exemplo1

public class Exemplo1 {

   // O programa sempre tem um "método" main que é onde começa a execução

   public static void main(String args[]) {

 

      // Abra uma conta de número 1 para João com CPF 309140605-06

      // A conta será "referenciada" com a variável umaConta

      ContaSimples1 umaConta = new ContaSimples1("Joao", "30914060506", 1);

 

      // Nesta conta, deposite R$1000,00

      umaConta.depositar(1000.0);

 

      // Imprima o saldo da conta de João

      double saldo = umaConta.getSaldo();

      System.out.print("Saldo da conta de Joao antes do saque: ");

      System.out.println(saldo);

 

      // Saque R$300,00 desta conta

      umaConta.sacar(300.0);

 

      // Imprima o objeto umaConta

      System.out.println(umaConta);

 

      // Imprima o saldo da conta de João

      System.out.println("Saldo da conta de Joao depois do saque: "

            + umaConta.getSaldo());

   } // fim do método main

} // fim da classe Exemplo1

O exemplo acima é praticamente idêntico ao exemplo Banco1.java

Observe que o método main está em Exemplo1 e não em ContaSimples1

O próximo exemplo está em ContaSimples1.java e consiste em colocar o main diretamente na classe que definimos acima

package p2.exemplos;

 

/**

 * Classe de conta bancária simples.

 *

 * @author Jacques Sauvé

 * @version 2.0

 */

public class ContaSimples1 {

   private String nome;

 

   private String cpf;

 

   private int numero;

 

   private double saldo;

 

   // construtor

   /**

    * Cria uma conta a partir de um nome e cpf de pessoa física, e um número de

    * conta.

    *

    * @param nome

    *            O nome do titular da conta.

    * @param cpf

    *            O CPF do titular da conta.

    * @param número

    *            O número da conta.

    */

   public ContaSimples1(String nome, String cpf, int numero) {

      this.nome = nome;

      this.cpf = cpf;

      this.numero = numero;

      this.saldo = 0.0;

   }

 

   // métodos

 

   /**

    * Recupera o nome do titular da conta.

    *

    * @return O nome do titular da conta.

    */

   public Object getNome() {

      return this.nome;

   }

 

   /**

    * Recupera o CPF do titular da conta.

    *

    * @return O CPF do titular da conta.

    */

   public Object getCPF() {

      return this.cpf;

   }

 

   /**

    * Recupera o número da conta.

    *

    * @return O número da conta.

    */

   public Object getNúmero() {

      return this.numero;

   }

 

   /**

    * Recupera o saldo da conta.

    *

    * @return O saldo da conta.

    */

   public double getSaldo() {

      return this.saldo;

   }

 

   /**

    * Efetua um depósito numa conta.

    *

    * @param valor

    *            O valor a depositar.

    */

   public void depositar(double valor) {

      this.saldo += valor;

   }

 

   /**

    * Efetua sacada na conta.

    *

    * @param valor

    *            O valor a sacar.

    * @return O sucesso ou não da operação.

    */

   public boolean sacar(double valor) {

      if (this.saldo >= valor) {

         this.saldo -= valor;

         return true;

      }

      return false;

   }

 

   /**

    * Transforma os dados da conta em um String.

    *

    * @return O string com os dados da conta.

    */

   public String toString() {

      return "numero " + numero + ", nome " + nome + ", saldo " + saldo;

   }

 

   // O programa sempre tem um "método" main que é onde começa a execução

   public static void main(String args[]) {

      // Abra uma conta de número 1 para João com CPF 309140605-06

      // A conta será "referenciada" com a variável umaConta

      ContaSimples1 umaConta = new ContaSimples1("Joao", "30914060506", 1);

      // Nesta conta, deposite R$1000,00

      umaConta.depositar(1000.0);

 

      // Imprima o saldo da conta de João

      double saldo = umaConta.getSaldo();

      System.out.print("Saldo da conta de Joao antes do saque: ");

      System.out.println(saldo);

 

      // Saque R$300,00 desta conta

      umaConta.sacar(300.0);

      // Imprima o objeto umaConta

      System.out.println(umaConta);

      // Imprima o saldo da conta de João

      System.out.println("Saldo da conta de Joao depois do saque: "

            + umaConta.getSaldo());

   } // fim do método main

 

}

Observe que "main" é um método de classe (por causa da palavra "static")

     Pode executar sem ter objeto em existência ainda

     É assim que a bola começa a rolar e objetos são criados, etc.

 

Palavras adicionais sobre o exemplo

Como na programação não OO, o “método” é uma técnica de ocultação de informação

     Para poder diminuir a complexidade, focando o programador numa coisa só

Observe como os métodos escodem os detalhes internos do objeto

     Para quem está “fora”, só usando o objeto, sabemos que podemos fazer um saque e um depósito mas nada sabemos sobre como isso ocorre, internamente

     Isso é uma chave da programação!

Também estamos vendo a técnica de encapsulamento em ação

     Dados (saldo, etc.) foram encapsulados numa caixa preta e a caixa disponibiliza uma interface (os métodos) para manipular os dados que estão dentro da caixa

     Isso é melhor do que acessar diretamente os dados para manipulação

     É melhor perguntar a alguém o que ele tomou no café da manhã ou enfiar sua mão goela abaixo e puxar a gosma para descobrir ...?

Observe como os métodos são pequenos

     Isso é normal na orientação a objeto

     Você deve desconfiar de métodos grandes: devem ser complicados demais e deveriam ser quebrados

 

Vamos falar de testes novamente ...

Os testes da classe ContaSimples1 não estão completos

Principalmente, as condições de “exceção” não foram testadas

Exemplos:

     Construtor

i)    O que ocorre se o nome for nulo ou vazio?

ii)O que ocorre se o CPF for nulo ou vazio?

iii)                      O que ocorre se o CPF for inválido?

iv)                      O que ocorre se o número da conta não for positivo?

     depositar

i)    O que ocorre se o valor 0.0 for depositado?

ii)O que ocorre se um valor negativo for depositado?

iii)                      Depositar centavos funciona?

iv)                      O que ocorre se depositar frações de centavos?

     sacar

i)    O que ocorre se o valor 0.0 for sacado?

ii)O que ocorre se um valor negativo for sacado?

iii)                      Sacar centavos funciona?

iv)                      O que ocorre se sacar frações de centavos?

     toString

i)    toString não foi testado

ii)Tem que testar com que valores de saldo?

(1)       Zero

(2)       Positivo

(3)       Com centavos

Muitos testes são necessários para garantir que tratamos adequadamente de todas as situações

     Para que testar se não for assim?

É o que veremos agora ...

…Mas antes precisamos aprender como lidar com os erros que possam ocorrer em nossos programas de forma elegante.

 

Tratamento de Erros

É importante diferenciar o descobrimento do erro e o tratamento do erro

     É muito frequente descobrir algo errado em um lugar mas querer tratar o erro em outro lugar

     Por exemplo, tratar o erro de nome vazio em ContaSimples1() é ruim porque é um método de "baixo nível" que não sabe sequer que tipo de interface está sendo usada (gráfica, a caractere), etc.

i)    Não seria apropriado fazer um println e exit

A solução OO: Exceções

Vamos usar um mecanismo novo para retornar erros

     O retorno normal de valores por um método usa "return"

     O retorno anormal (indicando erro) usa outra palavra para retornar do método

i)    A palavra é throw

     Da mesma forma que "return", "throw" retorna imediatamente do método

     Diferentemente de "return", "throw" só retorna objetos especiais chamados exceções

     A exceção pode conter uma mensagem indicando o erro que ocorreu para que o cliente da classe tenha informação suficiente para tratar o erro adequadamente

"throw" faz com que todos os métodos chamados retornem, até o ponto em que algum método captura a exceção para tratar o erro

     Essa captura é feita com um bloco try-catch

“throws” é uma palavra reservada usada para indicar que um método pode lançar uma exceção

     Assim, o código que chama este método pode se preparar para capturar a exceção com try-catch ou relançar a exceção (neste caso deve também indicar isso em sua assinatura)

Segue um exemplo do uso de exceções para você fuçar

package p2.exemplos;

 

/**

 * Classe que mostra o uso de exceções.

 *

 * @author Jacques Philippe Sauvé

 *

 */

public class TesteDeExcecoes {

   public static void main(String[] args) {

      new TesteDeExcecoes().doIt();

      System.out.println("bye, bye");

   }

 

   private void doIt() {

      try {

         System.out.println("doIt: chama a()");

         a(false);

         System.out.println("doIt: a() retornou");

         System.out.println("main: chama b()");

         b(false);

         System.out.println("doIt: b() retornou");

         System.out.println("doIt: nao capturou excecao");

      } catch (Exception ex) {

         System.out.println("doIt: capturou excecao: " + ex.getMessage());

      }

   }

 

   private void a(boolean lanca) throws Exception {

      System.out.println("a: chama c(" + lanca + ")");

      c(lanca);

      System.out.println("a: c() retornou");

   }

 

   private void c(boolean lanca) throws Exception {

      System.out.println("c: inicio");

      if (lanca) {

         System.out.println("c: vai lancar");

         throw new Exception("bomba!");

//       System.out.println("c: lancou"); // causaria erro de compilação

      }

      System.out.println("c: fim");

   }

 

   private void b(boolean lanca) throws Exception {

      try {

         System.out.println("b: chama c(" + lanca + ")");

         c(lanca);

         System.out.println("b: c() retornou sem excecao");

      } catch (Exception ex) {

         System.out.println("b: capturou excecao: " + ex.getMessage());

         // tire o comentário abaixo para ver o comportamento

         // throw ex;

         // ou então

         // throw new Exception("granada!");

      } finally {

         System.out.println("b: finally");

      }

   }

}

Agora, vamos ver como montar esse circo

     Vamos brincar com a classe teste de exceções, até entender bem como o lançamento e a captura de exceções ocorre em Java

 

Eis a classe de testes com novos testes

     Vamos testar a classe ContaSimples2 que trata erros adequadamente

     Os testes estão em TestaContaSimples2.java

package p2.exemplos.tests;

 

import org.junit.Assert;

import org.junit.Before;

import org.junit.Test;

 

import p2.exemplos.ContaSimples2;

 

public class TestaContaSimples2 {

 

   private ContaSimples2 umaConta;

 

   @Before

   public void criaConta() throws Exception {

      umaConta = new ContaSimples2("Jacques Sauve", "123456789-01", 123);

   }

 

   @Test

   public void TestContaSimples1() throws Exception {

      Assert.assertEquals("Nome errado", "Jacques Sauve",
                           
umaConta.getNome());

      Assert.assertEquals("cpf errado", "123456789-01", umaConta.getCPF());

      Assert.assertEquals("Número errado", 123, umaConta.getNúmero());

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

      umaConta.depositar(100.0);

      Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005);

      Assert.assertTrue("Nao consegui sacar", umaConta.sacar(45.0));

      Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005);

      Assert.assertFalse("Consegui sacar demais", umaConta.sacar(56.0));

      Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005);

   }

 

   @Test

   public void testaErrosNoConstrutor() {

      try {

         ContaSimples2 umaConta = new ContaSimples2("", "123456789-01", 123);

         Assert.fail("Esperava excecao de nome vazio");

      } catch (Exception ex) {

         Assert.assertEquals("Mensagem errada",

               "Nome nao pode ser nulo ou vazio", ex.getMessage());

      }

      try {

         ContaSimples2 umaConta = new ContaSimples2(null, "123456789-01",

                                                    123);

         Assert.fail("Esperava excecao de nome nulo");

      } catch (Exception ex) {

         Assert.assertEquals("Mensagem errada",

               "Nome nao pode ser nulo ou vazio", ex.getMessage());

      }

      try {

         ContaSimples2 umaConta = new ContaSimples2("Jacques Sauve", "",

                                                    123);

         Assert.fail("Esperava excecao de CPF vazio");

      } catch (Exception ex) {

         Assert.assertEquals("Mensagem errada",

               "CPF nao pode ser nulo ou vazio", ex.getMessage());

      }

      try {

         ContaSimples2 umaConta = new ContaSimples2("Jacques Sauve", null,

               123);

         Assert.fail("Esperava excecao de CPF nulo");

      } catch (Exception ex) {

         Assert.assertEquals("Mensagem errada",

               "CPF nao pode ser nulo ou vazio", ex.getMessage());

      }

      try {

         ContaSimples2 umaConta = new ContaSimples2("Jacques Sauve",

               "123456789-01", 0);

         Assert.fail("Esperava excecao de numero de conta errada");

      } catch (Exception ex) {

         Assert.assertEquals("Mensagem errada",

               "Número da conta deve ser positivo", ex.getMessage());

      }

      try {

         ContaSimples2 umaConta = new ContaSimples2("Jacques Sauve",

               "123456789-01", -1);

         Assert.fail("Esperava excecao de numero de conta errada");

      } catch (Exception ex) {

         Assert.assertEquals("Mensagem errada",

               "Número da conta deve ser positivo", ex.getMessage());

      }

   }

 

   @Test

   public void testDepositar() throws Exception {

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

      umaConta.depositar(100.0);

      Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005);

      umaConta.depositar(0.0);

      Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005);

      try {

         umaConta.depositar(-0.01);

         Assert.fail("Esperava excecao no deposito");

      } catch (Exception ex) {

         Assert.assertEquals("Mensagem errada",

               "Deposito nao pode ser negativo", ex.getMessage());

      }

      umaConta.depositar(0.01);

      Assert.assertEquals("Saldo errado", 100.01, umaConta.getSaldo(),

                           0.005);

   }

 

   @Test

   public void testSacar() throws Exception {

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

      Assert.assertTrue("Nao consegui sacar", umaConta.sacar(0.0));

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

      umaConta.depositar(100.0);

      Assert.assertTrue("Nao consegui sacar", umaConta.sacar(23.10));

      Assert.assertEquals("Saldo errado", 76.90, umaConta.getSaldo(), 0.005);

      Assert.assertFalse("Consegui sacar demais", umaConta.sacar(76.91));

      Assert.assertTrue("Nao consegui sacar", umaConta.sacar(76.90));

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

      try {

         umaConta.sacar(-0.01);

         Assert.fail("Esperava excecao no saque");

      } catch (Exception ex) {

         Assert.assertEquals("Mensagem errada",

               "Saque nao pode ser negativo", ex.getMessage());

      }

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

   }

 

   @Test

   public void testToString() throws Exception {

      Assert.assertEquals("toString errado",

            "numero 123, nome Jacques Sauve, saldo 0.0", umaConta

                  .toString());

      umaConta.depositar(1.23);

      Assert.assertEquals("toString errado",

            "numero 123, nome Jacques Sauve, saldo 1.23", umaConta

                  .toString());

   }

}

Vamos escrever a classe ContaSimples2 com base nestes testes?

     Lembram que os testes são o roteiro de como a classe funciona?

A classe ContaSimples2 está a seguir

package p2.exemplos;

 

/**

 * Classe de conta bancária simples.

 *

 * @author Jacques Sauvé

 * @version 2.0

 */

public class ContaSimples2 {

   private static final Object STRING_VAZIA = "";

 

   private String nome;

 

   private String cpf;

 

   private int numero;

 

   private double saldo;

 

   // construtor

   /**

    * Cria uma conta a partir de um nome e cpf de pessoa física, e um número de

    * conta.

    *

    * @param nome

    *            O nome do titular da conta.

    * @param cpf

    *            O CPF do titular da conta.

    * @param número

    *            O número da conta.

    * @throws Exception

    *             Se o nome for nulo ou vazio, o CPF for nulo ou vazio ou a

    *             conta não for um número positivo

    */

   public ContaSimples2(String nome, String cpf, int numero) throws Exception {

      if (nome == null || nome.equals(STRING_VAZIA)) {

         throw new Exception("Nome nao pode ser nulo ou vazio");

      }

      if (cpf == null || cpf.equals(STRING_VAZIA)) {

         throw new Exception("CPF nao pode ser nulo ou vazio");

      }

      if (numero <= 0) {

         throw new Exception("Número da conta deve ser positivo");

      }

      this.nome = nome;

      this.cpf = cpf;

      this.numero = numero;

      this.saldo = 0.0;

   }

 

   // métodos

 

   /**

    * Recupera o nome do titular da conta.

    *

    * @return O nome do titular da conta.

    */

   public String getNome() {

      return nome;

   }

 

   /**

    * Recupera o CPF do titular da conta.

    *

    * @return O CPF do titular da conta.

    */

   public String getCPF() {

      return cpf;

   }

 

   /**

    * Recupera o número da conta.

    *

    * @return O número da conta.

    */

   public int getNúmero() {

      return numero;

   }

 

   /**

    * Recupera o saldo da conta.

    *

    * @return O saldo da conta.

    */

   public double getSaldo() {

      return saldo;

   }

 

   /**

    * Efetua um depósito numa conta.

    *

    * @param valor

    *            O valor a depositar.

    * @throws Exception

    *             Quando o valor a depositar é negativo.

    *

    */

   public void depositar(double valor) throws Exception {

      if (valor < 0.0) {

         throw new Exception("Deposito nao pode ser negativo");

      }

      saldo += valor;

   }

 

   /**

    * Efetua saque na conta.

    *

    * @param valor

    *            O valor a sacar.

    * @return O sucesso ou não da operação.

    * @throws Exception

    *             Para um valor de saque negativo

    */

   public boolean sacar(double valor) throws Exception {

      if (valor < 0.0) {

         throw new Exception("Saque nao pode ser negativo");

      }

      if (saldo >= valor) {

         saldo -= valor;

         return true;

      }

      return false;

   }

 

   /**

    * Transforma os dados da conta em um String.

    *

    * @return O string com os dados da conta.

    */

   public String toString() {

      return "numero " + numero + ", nome " + nome + ", saldo " + saldo;

   }

 

   // O programa sempre tem um "método" main que é onde começa a execução

   public static void main(String args[]) {

      // Abra uma conta de número 1 para João com CPF 309140605-06

      // A conta será "referenciada" com a variável umaConta

      ContaSimples2 umaConta = null;

      try {

         umaConta = new ContaSimples2("Joao", "30914060506", 1);

         // Nesta conta, deposite R$1000,00

         umaConta.depositar(1000.0);

      } catch (Exception e) {

         System.out.println(e.getMessage());

      }

 

      // Imprima o saldo da conta de João

      double saldo = umaConta.getSaldo();

      System.out.print("Saldo da conta de Joao antes do saque: ");

      System.out.println(saldo);

 

      // Saque R$300,00 desta conta

      try {

         umaConta.sacar(300.0);

      } catch (Exception e) {

         System.out.println(e.getMessage());

      }

      // Imprima o objeto umaConta

      System.out.println(umaConta);

      // Imprima o saldo da conta de João

      System.out.println("Saldo da conta de Joao depois do saque: "

            + umaConta.getSaldo());

   } // fim do método main

}

Uma outra forma de escrever alguns métodos de teste que lidam com as exceções segue

package p2.exemplos;

 

import org.junit.Assert;

import org.junit.Before;

import org.junit.Test;

 

import p2.exemplos.ContaSimples2;

 

public class TestaContaSimples2Outra {

 

   private ContaSimples2 umaConta;

 

   @Before

   public void criaConta() throws Exception {

      umaConta = new ContaSimples2("Jacques Sauve", "123456789-01", 123);

   }

 

   @Test

   public void TestContaSimples2() throws Exception {

      Assert.assertEquals("Nome errado", "Jacques Sauve", umaConta.getNome());

      Assert.assertEquals("cpf errado", "123456789-01", umaConta.getCPF());

      Assert.assertEquals("Número errado", 123, umaConta.getNúmero());

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

      umaConta.depositar(100.0);

      Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(), 0.005);

      Assert.assertTrue("Nao consegui sacar", umaConta.sacar(45.0));

      Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005);

      Assert.assertFalse("Consegui sacar demais", umaConta.sacar(56.0));

      Assert.assertEquals("Saldo errado", 55.0, umaConta.getSaldo(), 0.005);

   }

 

   @Test(expected = Exception.class)

   public void testaConstrutorNomeVazio() throws Exception {

      ContaSimples2 umaConta = new ContaSimples2("", "123456789-01", 123);

   }

 

   @Test(expected = Exception.class)

   public void testaConstrutorNomeNulo() throws Exception {

      ContaSimples2 umaConta = new ContaSimples2(null, "123456789-01", 123);

   }

 

   @Test(expected = Exception.class)

   public void testaConstrutorCPFVazio() throws Exception {

      ContaSimples2 umaConta = new ContaSimples2("Jacques Sauve", "", 123);

   }

 

   @Test(expected = Exception.class)

   public void testaConstrutorCPFNulo() throws Exception {

      ContaSimples2 umaConta = new ContaSimples2("Jacques Sauve", null,

                                                 123);

   }

 

   @Test(expected = Exception.class)

   public void testaConstrutorContaNumeroZero() throws Exception {

      ContaSimples2 umaConta = new ContaSimples2("Jacques Sauve",

            "123456789-01", 0);

   }

 

   @Test(expected = Exception.class)

   public void testaConstrutorContaNumeroNegativo() throws Exception {

      ContaSimples2 umaConta = new ContaSimples2("Jacques Sauve",

            "123456789-01", -1);

   }

 

   @Test

   public void testDepositar() throws Exception {

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

      umaConta.depositar(100.0);

      Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(),

                           0.005);

      umaConta.depositar(0.0);

      Assert.assertEquals("Saldo errado", 100.0, umaConta.getSaldo(),

                           0.005);

      try {

         umaConta.depositar(-0.01);

         Assert.fail("Esperava excecao no deposito");

      } catch (Exception ex) {

         Assert.assertEquals("Mensagem errada",

               "Deposito nao pode ser negativo", ex.getMessage());

      }

      umaConta.depositar(0.01);

      Assert.assertEquals("Saldo errado", 100.01, umaConta.getSaldo(),

                          0.005);

   }

 

   @Test

   public void testSacar() throws Exception {

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

      Assert.assertTrue("Nao consegui sacar", umaConta.sacar(0.0));

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

      umaConta.depositar(100.0);

      Assert.assertTrue("Nao consegui sacar", umaConta.sacar(23.10));

      Assert.assertEquals("Saldo errado", 76.90, umaConta.getSaldo(),

                          0.005);

      Assert.assertFalse("Consegui sacar demais", umaConta.sacar(76.91));

      Assert.assertTrue("Nao consegui sacar", umaConta.sacar(76.90));

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

      try {

         umaConta.sacar(-0.01);

         Assert.fail("Esperava excecao no saque");

      } catch (Exception ex) {

         Assert.assertEquals("Mensagem errada",

               "Saque nao pode ser negativo", ex.getMessage());

      }

      Assert.assertEquals("Saldo errado", 0.0, umaConta.getSaldo(), 0.005);

   }

 

   @Test

   public void testToString() throws Exception {

      Assert.assertEquals("toString errado",

            "numero 123, nome Jacques Sauve, saldo 0.0", umaConta

                  .toString());

      umaConta.depositar(1.23);

      Assert.assertEquals("toString errado",

            "numero 123, nome Jacques Sauve, saldo 1.23", umaConta

                  .toString());

   }

}

 

Nossa segunda classe: com vários construtores

A classe Pessoa

Em primeiro lugar, queremos ver a implementação da classe Pessoa que usamos no passado

Exercício para casa:

     Escreva testes para a classe Pessoa

package p1.aplic.geral;

 

import java.io.*;

 

/**

 * Classe representando uma pessoa física.

 *

 * @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br

 * @version 1.0 <br>

 *          Copyright (C) 1999 Universidade Federal de Campina Grande.

 */

 

public class Pessoa implements Serializable {

   private String nome;

   private String cpf;

 

   // Construtores

   /**

    * Constroi uma pessoa com nome e CPF dados.

    *

    * @param nome

    *            O nome da pessoa.

    *

    * @param cpf

    *            O CPF da pessoa.

    */

   public Pessoa(String nome, String cpf) {

      this.nome = nome;

      this.cpf = cpf;

   }

 

   /**

    * Constroi uma pessoa com nome dado e sem CPF.

    *

    * @param nome

    *            O nome da pessoa.

    */

   public Pessoa(String nome) {

      this(nome, "");

   }

 

   /**

    * Recupera o nome da pessoa.

    *

    * @return O nome da pessoa.

    */

   public String getNome() {

      return nome;

   }

 

   /**

    * Recupera o CPF da pessoa.

    *

    * @return O CPF associado à pessoa.

    */

   public String getCPF() {

      return cpf;

   }

 

   /**

    * Ajusta o nome da pessoa.

    *

    * @param nome

    *            O nome da pessoa.

    */

   public void setNome(String nome) {

      this.nome = nome;

   }

 

   /**

    * Ajusta o CPF da pessoa.

    *

    * @param cpf

    *            O CPF associado à pessoa.

    */

   public void setCPF(String cpf) {

      this.cpf = cpf;

   }

 

   /**

    * Representa a pessoa como string

    */

   public String toString() {

      return "Nome " + nome + ", cpf " + cpf;

   }

 

   /**

    * Testa a igualdade de um objeto com esta pessoa.

    *

    * @param objeto

    *            O objeto a comparar com esta pessoa.

    * @return true se o objeto for igual a esta pessoa, false caso contrário.

    */

   public boolean equals(Object objeto) {

      if (!(objeto instanceof Pessoa)) {

         return false;

      }

      Pessoa outra = (Pessoa) objeto;

      return getNome().equals(outra.getNome())

            && getCPF().equals(outra.getCPF());

   }

}

Esqueça, por enquanto da palavra "Serializable"

     Por enquanto, significa apenas que queremos gravar objetos dessa classe em arquivo

A linha com “package” diz a qual pacote pertence a classe

Temos dois construtores

     Há um overload do nome Pessoa

   // Construtores

   /**

    * Constroi uma pessoa com nome e CPF dados.

    *

    * @param nome

    *            O nome da pessoa.

    *

    * @param cpf

    *            O CPF da pessoa.

    */

   public Pessoa(String nome, String cpf) {

      this.nome = nome;

      this.cpf = cpf;

   }

 

   /**

    * Constroi uma pessoa com nome dado e sem CPF.

    *

    * @param nome

    *            O nome da pessoa.

    */

   public Pessoa(String nome) {

      this(nome, "");

   }

Uma Pessoa pode ser criada de duas formas: com e sem CPF

Pessoa pessoa1 = new Pessoa("Raquel", "98765432-11");

Pessoa pessoa2 = new Pessoa("Raquel");

Observe também que o segundo construtor chama this() como se this fosse um método

     this(...) é a chamada a um construtor da classe, neste caso com dois argumentos

     Isto é, Pessoa(String) chama Pessoa(String, String), passando o string nulo como CPF

     É uma forma de não duplicar código (fatorando o que é igual)

Observe a existência de métodos "mutator" (que alteram o valor dos atributos)

Finalmente, é muito comum uma classe incluir um método equals() para testar se outro objeto qualquer é igual a este (que foi chamado)

/**

    * Testa a igualdade de um objeto com esta pessoa.

    *

    * @param objeto

    *            O objeto a comparar com esta pessoa.

    * @return true Se o objeto for igual a esta pessoa, false caso
   
*              contrário.

    */

   public boolean equals(Object objeto) {

      if (!(objeto instanceof Pessoa)) {

         return false;

      }

      Pessoa outra = (Pessoa) objeto;

      return getNome().equals(outra.getNome())

            && getCPF().equals(outra.getCPF());

   }

     Dá para ver que dois objetos Pessoa são iguais se tiverem nome e CPF iguais

     Entenderemos adiante que Object é um objeto de classe geral e instanceof diz se um objeto “é” de uma certa classe

     Por que usamos getNome() e getCPF() em vez de usar nome e cpf???

Usando a classe Pessoa

Agora vamos usar a classe Pessoa, através de uma outra classe ContaSimples3.java

package p2.exemplos;

 

import p1.aplic.geral.Pessoa;

 

/**

 * Classe de conta bancária simples.

 *

 * @author Jacques Sauvé

 * @author Raquel Lopes

 * @version 3.0

 */

public class ContaSimples3 {

   private static final Object STRING_VAZIA = "";

 

   private Pessoa titular;

 

   private int numero;

 

   private double saldo;

 

   // construtores

 

   /**

    * Cria uma conta a partir de uma pessoa e número de conta.

    *

    * @param titular

    *            O titular da conta.

    * @param número

    *            O número da conta.

    */

   public ContaSimples3(Pessoa titular, int numero) throws Exception {

      if (titular.getNome() == null ||

          titular.getNome().equals(STRING_VAZIA)) {

         throw new Exception("Nome nao pode ser nulo ou vazio");

      }

      if (titular.getCPF() == null ||

          titular.getCPF().equals(STRING_VAZIA)) {

         throw new Exception("CPF nao pode ser nulo ou vazio");

      }

      if (numero <= 0) {

         throw new Exception("Número da conta deve ser positivo");

      }

      this.titular = titular;

      this.numero = numero;

      this.titular = titular;

      saldo = 0.0;

   }

 

   /**

    * Cria uma conta a partir de um nome e cpf de pessoa física, e um número

    * de conta.

    *

    * @param nome

    *            O nome do titular da conta.

    * @param cpf

    *            O CPF do titular da conta.

    * @param número

    *            O número da conta.

    * @throws Exception

    *             Se o nome for nulo ou vazio, o CPF for nulo ou vazio ou a

    *             conta não for um número positivo

    */

   public ContaSimples3(String nome, String cpf, int numero)

                                                      throws Exception {

      this(new Pessoa(nome, cpf), numero);

   }

 

   // métodos

 

   /**

    * Recupera o nome do titular da conta.

    *

    * @return O nome do titular da conta.

    */

   public String getNome() {

      return titular.getNome();

   }

 

   /**

    * Recupera o CPF do titular da conta.

    *

    * @return O CPF do titular da conta.

    */

   public String getCPF() {

      return titular.getCPF();

   }

 

   /**

    * Recupera o número da conta.

    *

    * @return O número da conta.

    */

   public int getNúmero() {

      return numero;

   }

 

   /**

    * Recupera o titular da conta.

    *

    * @return O titular da conta.

    */

   public Pessoa getTitular() {

      return titular;

   }

 

   /**

    * Recupera o saldo da conta.

    *

    * @return O saldo da conta.

    */

   public double getSaldo() {

      return saldo;

   }

 

   /**

    * Efetua um depósito numa conta.

    *

    * @param valor

    *            O valor a depositar.

    * @throws Exception

    *             Quando o valor a depositar é negativo.

    *

    */

   public void depositar(double valor) throws Exception {

      if (valor < 0.0) {

         throw new Exception("Deposito nao pode ser negativo");

      }

      saldo += valor;

   }

 

   /**

    * Efetua saque na conta.

    *

    * @param valor

    *            O valor a sacar.

    * @return O sucesso ou não da operação.

    * @throws Exception

    *             Para um valor de saque negativo

    */

   public boolean sacar(double valor) throws Exception {

      if (valor < 0.0) {

         throw new Exception("Saque nao pode ser negativo");

      }

      if (saldo >= valor) {

         saldo -= valor;

         return true;

      }

      return false;

   }

 

   /**

    * Transforma os dados da conta em um String.

    *

    * @return O string com os dados da conta.

    */

   public String toString() {

      return "numero " + numero + ", nome " + getNome() + ", saldo " + saldo;

   }

}

Observe particularmente os seguintes pontos:

     Os dois construtores com overload da palavra Pessoa como método

     Como o segundo construtor chama o primeiro

     Como variáveis temporárias são evitadas no segundo construtor

     O que getTitular() retorna

     Como getNome() e getCPF() fazem seu trabalho

Você vê por qual motivo toString() chama getNome() em vez de usar titular.getNome()?

Em UML, a relação entre as duas classes pode ser vista assim:

     A linha é uma associação (ou relação) entre classes

     Neste caso, é uma associação de “conhecimento” (ContaSimples3 conhece uma Pessoa)

     A seta indica a navegabilidade

 

ProgramaHP da disciplina