[Pesquisar este blog]

terça-feira, 15 de maio de 2018

Robocode 101

Não é novidade que existe grande interesse no desenvolvimento de jogos digitais por conta do enorme mercado que existe. Mas nem todo mundo sabe que criar jogos digitais auxilia, e muito, no desenvolvimento de habilidade de programação que servem para muito mais do que os jogos.

Por isto é que existe tanto interesse em jogos digitais e competições, principalmente envolvendo estudantes, para identificar novos talentos.

O robocode é um ambiente de programação de jogos digitais, de código livre, onde tanques-de-guerra-robôs competem virtualmente, de maneira autônoma, em batalhas do tipo vale-tudo (o último sobrevivente é o vencedor). Tal plataforma pode ser utilizada como ambiente para o desenvolvimento de tais habilidade de programação e também para conduzir competições de diferentes estilos e níveis.

Segue um material de orientação para aqueles que estão interessados no assunto. Aviso que este material é uma reorganização de outros que já publiquei aqui mesmo no Tecnopode.

Bom proveito!

Para saber mais


  • Home
    http://robocode.sourceforge.net/
  • Download
    https://sourceforge.net/projects/robocode/files/
  • Documentação OnLine
    http://robocode.sourceforge.net/docs/robocode/
  • Wiki
    http://robowiki.net/wiki/Robocode
  • Artigos
    http://robocode.sourceforge.net/developerWorks.php
  • UFSC
    https://www.gsigma.ufsc.br/~popov/aulas/robocode/index.html
  • YouTube vídeo
    http://www.youtube.com/watch?v=3qOoDuGkwco 
  • quinta-feira, 5 de abril de 2018

    POO::Plena-25-Classes Internas e Anônimas

    POO-P-24-Polimorfismo, upcasting e downcastinPOO-A-26-Tratamento Avançado de Exceções
    Os mecanismos de definição de classe presentes todas as linguagens de programação OO permitem o aninhamento de classes, criando o que se denominam classes internas. Assim é possível criar uma classe contida em outra classe, que pode estar contida em outra e, assim, sucessivamente.

    Classes aninhadas e internas

    O aninhamento de classes permite que uma classe seja declarada como membro de outra, o que corresponde a uma sintaxe de natureza recursiva [4][6][7]. Toda classe aninhada (nested class) é membro de uma classe externa.

    Tal como a classe externa, a classe aninhada pode conter campos, métodos, construtores e outras classes aninhadas, como mostra a figura anterior.

    O exemplo que segue mostra uma classe denominada Externa, que possui alguns campos, métodos e construtores, públicos e privados, incluindo um membro que é uma classe aninhada pública denominada Aninhada.

    public class Externa {
       private double valor1;
       public int valor2;

       public Externa() { this(1.5); }
       public Externa(double valor1) {
          this.valor1 = valor1;
       }

       public void exibir() {
          System.out.printf("%f, %d\n", valor1, valor2);
       }

       public class Aninhada {
          private float valor3;

          public Aninhada(float valor3) {
             this.valor3 = valor3;
          }

          public double produto() {
             return valor1 * valor2 * valor3;
          }
          public void exibir() {
             System.out.printf("%f, %d, %f\n",
                valor1, valor2, valor3);
          }
       }
    }

    A classe Aninhada, que é membro público da classe Externa, também possui campos, métodos e construtores, públicos e privados. As regras de acesso, isto é, a visibilidade dos seus membros é como sempre.

    A classe aninhada, como membro de sua classe externa, possui acesso irrestrito a todos os membros  desta, independentemente de sua visibilidade declarada [6][7]. Os membros públicos da classe aninhada serão visíveis externamente a classe, enquanto os privados serão exclusivos da implementação da classe.

    A instanciação de objetos da classe externa é feita como para qualquer classe comum, ou seja:

    // instanciação da classe Externa
    Externa externa1 = new Externa(20.16);
    Externa externa2 = new Externa(14.04);

    Já a instanciação de objetos da classe aninhada requer a existência de uma instância da sua classe externa para que um objeto do seu tipo possa ser instanciado. Isto evidencia o tipo de relacionamento de dependência que existe entre uma classe aninhada e a classe externa que a contém.

    // instanciação da classe Aninhada
    Externa.Aninhada aninhada1 = externa1. new Aninhada(1.2f);
    Externa.Aninhada aninhada2 = externa2. new Aninhada(3.4f);

    Na instanciação da classe Aninhada temos uma sintaxe que, de fato, não é familiar:
    • A declaração de um objeto da classe aninhada requer a qualificação completa: Externa.Aninhada.
    • A instanciação de objeto da classe aninhada requer uma instância da sua classe externa.
    • Além disso, se observa uma sintaxe não usual associada ao operador new!

    Já o uso dos objetos, tanto da classe externa como da aninhada, é feito da maneira usual, como mostra o programa simples que segue:

    public class TestaExternaAninhada {
       public static void main(String[] a) {
          // uso dos objetos da classe Externa
          externa1.valor2 = 1;
          externa1.exibir();

          // uso dos objetos da classe Aninhada
          System.out.println(aninhada1.produto());
          aninhada1.exibir();

          // uso dos objetos da classe Externa
          externa2.valor2 = 2;
          externa2.exibir();

          // uso dos objetos da classe Aninhada
          System.out.println(aninhada2.produto());
          aninhada2.exibir();
       }
    }

    Cada objeto da classe aninhada tanto faz referência ao seu objeto correspondente da classe externa, como tem acesso ao conteúdo específico desta instância, como ilustrado na figura que segue.
    O resultado ilustrado nesta figura mostra:
    • Linha_1:
      valores 20.16 e 1, armazenados nos campos valor1 e valor2 da instância da classe Externa denominada externa1, exibidos pelo seu método exibir();
    • Linha_2:
      valor 24.19..., resultado do método produto() acionado para a instância da classe Aninhada chamada aninhada1  (valor1*valor2*valor3=20.16*1*1.2);
    • Linha_3:
      valores 20.16, 1 e 1.2, resultado do método exibir() acionado para a instância da classe Aninhada denominada aninhada1;
    • Linha_4:
      valores 14.04 e 2, armazenados nos campos valor1 e valor2 da instância da classe Externa denominada externa2, exibidos pelo seu método exibir();
    • Linha_5:
      valor 95472..., resultado do método produto() acionado para a instância da classe Aninhada chamada aninhada2  (valor1*valor2*valor3=14,04*2*3.4);
    • Linha_6:
      valores 14,04, 2 e 3.4, resultado do método exibir() acionado para a instância da classe Aninhada denominada aninhada2.

    Com isso vemos que é possível utilizar membros de instâncias de classes aninhadas como de qualquer classe, apesar de que a forma de instanciação é um pouco diferente.

    Como a existência de instâncias das classes aninhadas depende de uma instância da classe externa, esse tipo de classe aninhada recebe o nome (mais comum) de classe interna ou inner class. A classe externa, por analogia, é conhecida como outter class [4][6][7].

    O uso destes objetos, após sua instanciação, é semelhante a qualquer outro, como já vimos.

    Classes aninhadas estáticas

    Uma classe aninhada pode ser declarada como um membro estático de sua classe externa, num outro uso do modificador static [4][6][7]. Desta forma a classe aninhada estática tem acesso irrestrito apenas aos membros estáticos da classe externa, independente de sua visibilidade.

    public class OutraExterna {
       private static int valor1;
       public int valor2;

       public static class AninhadaEstatica {
          public void exibir () {
             System.out.println("valor1 = "+ valor1);
          }
       }
    }

    Classes aninhadas estáticas não são consideradas classes internas, pois não existe uma relação de dependência forte como aquela existente nas classes internas, ou seja, como nas classes aninhadas como membros não estáticos.

    Outro aspecto particular é que a instanciação de classes aninhadas estáticas independe de instâncias da classe externa, sendo feito como segue:

    OutraExterna.AninhadaEstatica aninhadaEstatica;
    aninhadaEstatica = new OutraExterna.AninhadaEstatica();

    Aqui é possível ver que a declaração e instanciação de um objeto de uma classe aninhada estática requer apenas sua qualificação completa.

    O exemplo que segue mostra um programa que cria uma lista arbitrariamente longa de palavras. As palavras são armazenadas numa lista ligada simples, cujos nodos são objetos da classe No, uma classe aninhada estática. Como não se pretende que esta classe No seja utilizada por outros programas, seu caráter auxiliar e privado mostra como é conveniente sua declaração como classe aninhada estática.

    public class Lista {
       // classe aninhada estática
       private static class No {
          public String nome;
          public No proximo;

          @SuppressWarnings("unused")
          public No() {
             this(null, null); // aciona construtor parametrizado
          }
          public No(String n, No p) {
             nome = n;
             proximo = p;
          }
       }

       public static void main (String arg[]) {
          No lista = null; // lista vazia (sem nós)
          Scanner sc = new Scanner(System.in); // prepara console
          do { // laço para solicitação de dados
             System.out.println("Digite uma palavra (. encerra):");
             String aux = sc.next();
             if (!aux.equals(".")) {
                No novo = new No(aux, lista);
                lista = novo;
             }
          } while (!aux.equals("."));
          System.out.println("Lista das Palavras:");
          No no = lista; // variável auxiliar para navegar lista
          while (no!=null) {
             System.out.print(no.nome + " ");
             no = no.proximo;
          }
          System.out.println("<.>");
       }
    } // Fim da classe Lista


    Classes anônimas

    Uma classe anônima (anonymous class) é uma classe interna, de papel auxiliar, declarada sem nome e definida como uma subclasse ou como a realização de uma interface específica [4][6][7].

    Tem o propósito de servir para a instanciação de um único objeto que, provavelmente, não será utilizado em outro contexto, portanto não requerendo que seja armazenada uma referência para o objeto criado.

    Considere a situação que segue, de definição de duas classes semelhantes destinadas a criação de uma thread.

    // Uma subclasse da classe Thread
    public class A extends Thread {
       public void run () {
       /* código de run omitido */
       }
    }
    // A realização da interface Runnable
    public class B implements Runnable {
       public void run () {
       /* código de run omitido */
       }
    }

    Objetos das classes A e B poderiam ser instanciados como:

    // Uma instância de A
    A objeto1 = new A ();

    // Uma instância de B
    B objeto2 = new B ();

    Mas, se apenas um objeto destas classes é usado, a definição de A ou B, como classes independentes, aninhadas ou internas é desnecessária, pois não serão reutilizadas para criação de outros objetos.

    O uso de classes anônimas é conveniente nestas situações, onde é possível substituir as classes A e B criadas anteriormente pelo código que segue:

    // Classe anônima derivada de Thread
    // e que equivale a classe A.
    Thread objeto1 = new Thread ( ) {
       public void run () {
       /* código de run omitido */
       }
    };

    // Classe anônima que realiza Runnable
    // e que equivale a classe B.
    Runnable objeto2 = new Runnable ( ) {
       public void run () {
       /* código de run omitido */
       }
    };

    Aqui o uso de um construtor é complementado com um bloco declarativo de código que:
    • implementa métodos abstratos necessários, ou
    • substitui métodos existentes da superclasse indicada; ou
    • realiza uma interface diretamente implementando os métodos nela especificados.

    Deve ser ressaltado que o bloco declarativo não é acompanhado de um nome específico, ou seja, temos uma classe sem nome, ou seja, anônima.

    Esta construção é conveniente quando é requerido apenas um objeto da classe, pois como a classe criada é anônima, não será possível utilizá-la novamente para criar outros objetos.

    Um uso bastante típico de classe anônimas é no registro de objetos processadores de eventos (listeners) em interfaces gráficas (GUI - Graphical User Interfaces), como nos fragmentos que seguem:

    // Instância de botão presente na GUI
    JButton button1 = new JButton("Fazer Ação");

    // Registro do listener: acionamento do botão
    // provoca invocação do método
    :
    button1.addActionListener( new ActionListener() {
       @Override
       public void actionPerformed(ActionEvent evt) {
          // método acionado pelo botão
          acao_Botao1(evt);
       }
    });
    :
    protected void acao_Botao1(ActionEvent evt) {
       // Ação correspondente do botão
    }

    A classe anônima utilizada neste exemplo realiza a interface java.awt.event.ActionListener, que requer apenas a implementação do método actionPerformed(Event), o qual "desvia" o acionamento do botão para outro método, declarado como membro da classe externa.

    Dado que são elementos auxiliares, as classes aninhadas somente devem ser criadas quando sua implementação se limita a dois ou três métodos simples. Classes aninhadas extensas pioram a legibilidade dos programas e devem ser evitadas.

    Ao mesmo tempo, as classes anônimas são muito utilizadas: na criação de iterators (classes para navegação de coleções); e no processamento de eventos em aplicações dotadas de interfaces gráficas. Nestas situações, permitem a definição rápida de subclasses e objetos que implementam interfaces específicas de maneira simples.

    POO-P-24-Polimorfismo, upcasting e downcastinPOO-A-26-Tratamento Avançado de Exceções

    Referências Bibliográficas

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

    segunda-feira, 2 de abril de 2018

    POO::Plena-24-Polimorfismo, upcasting e downcasting

    POO-P-23-PolimorfismoPOO-A-25-Classes internas e anônimas
    O polimorfismo é a característica mais importante da OO e consiste no mecanismo pelo qual vários tipos de objetos, ou seja, formas diferentes de implementação, podem ser tratadas como sendo de um tipo comum.
    Todas as linguagens orientadas a objetos possuem uma classe que constitui a raiz de sua hierarquia de tipos. A partir desta classe são criadas outras por meio da herança, a qual pode ser usada de maneira implícita ou explícita. Na plataforma Java a raiz da hierarquia é a classe java.lang.Object; enquanto que no C# a classe-raiz é System.Object.

    Desta maneira, qualquer que seja a hierarquia de classes considerada, todas as classes ali existentes são subclasses de Object, sendo assim, uma variável do tipo Object pode armazenar referências de qualquer tipo de objeto.

    O polimorfismo permite que um objeto seja, transparentemente, tratado como qualquer outro tipo ascendente (superclasses da qual é derivado), e também como sendo do seu tipo verdadeiro, isto é, como foi instanciado de fato. Isso possibilita a generalização, ou seja, uma operação que faz o contrário da herança, que permite a especialização.

    Como vimos, um objeto cuja classe é derivada de outra é uma composição de objetos em camadas. A camada mais externa corresponde ao tipo real do objeto (por exemplo Gerente) e nela se encontram as adições providas por esta classe (sua especialização em relação à camada anterior). A camada mais interna é sempre aquela que corresponde ao tipo Object, onde estão presentes seus elementos. As camadas intermediárias são aquelas fornecidas pelos descendentes de Object até a camada mais externa. A figura que segue ilustra esta estrutura em camadas de um objeto de uma possível classe Comissionado.

    Conforme o tipo da referência usada para acessar um objeto, determina-se quais camadas poderão efetivamente ser usadas. Se utilizamos uma referência do tipo real (de instanciação), temos acesso a todas os membros públicos deste tipo e de todos os tipos ancestrais (ascendentes). Na figura acima, a referência do tipo Comissionado permite acessar todos os membros públicos do objeto presentes na camada mais externa (Comissionado), na camada intermediária (Funcionario) e também na camada mais interna (Object).

    Mas se a referência é do tipo Funcionario, só é possível acessar os membros desta camada (Funcionario) e das mais internas (no caso apenas Object). Uma referência de tipo Funcionário não consegue acessar nada presente em camadas mais externas do que seu tipo, porque sua classe não prevê sua existência, pois foram adicionadas nas especializações (subclasses) posteriores.

    Da mesma maneira, uma referência do tipo Object só consegue acessar a porção referente a este tipo presente em objetos de qualquer classe.

    Por conta da construção dos objetos em múltiplas camadas e do comportamento dos diferentes tipos de referências em relação às camadas presentes nos objetos, temos duas importantes operações decorrentes do polimorfismo:
    • up type casting (ou apenas upcasting) e
    • down type casting (ou apenas downcasting).


    Polimorfismo e upcasting

    O uptype casting, upcasting ou coerção para supertipo é a operação disponível na orientação a objetos que permite que uma referência de superclasse utilize a porção correspondente nos objetos descendentes de sua hierarquia. Em outras palavras, uma referência de um certo tipo pode acessar sua camada correspondente em qualquer objeto de tipo derivado, isto é, em qualquer subclasse.

    Observe novamente a hierarquia de objetos. Funcionario (que é uma classe derivada de Object) dá origem a várias classes, tais como Caixa, Gerente, Motorista ou Comissionado.

    Uma referência do tipo Gerente permite acessar todas as camadas presentes nos objetos instanciados como deste tipo, assim como apenas uma referência do tipo Comissionado permite acessar todos os elementos deste tipo, como ilustrado na figura que segue.

    Referências deste tipo podem ser obtidas como no fragmento abaixo:

    Gerente g2 = new Gerente(2345);
    Comissionado c2 = new Comissionado(3456);

    No entanto, como um Gerente é um Funcionario (é subclasse deste tipo e possui uma camada interna correspondente), uma referência do tipo Funcionario pode ser usada para acessar tal camada (e as interiores) em qualquer objeto do tipo Gerente ou Comissionado, pois é garantido que tais objetos possuem a camada interior correspondente a Funcionario.

    Por meio do polimorfismo, uma referência de tipo Funcionario pode acessar objetos de tipos diferentes, embora tal acesso seja limitado a camada correspondente à referência, portanto de objetos cujos tipos são subclasses do tipo da referência. Assim é válido fazer:

    // Funcionario é superclasse de Gerente
    // Gerente é subclasse de Funcionario
    Funcionario f2 = g2;
    Funcionario f3 = new Gerente(2346);

    // Funcionario é superclasse de Comissionado
    // Comissionado é subclasse de Funcionario
    Funcionario f4 = c2;
    Funcionario f5 = new Comissionado(3457);

    Aqui foi usado o upcasting, ou seja, uma referência de um tipo ancestral (Funcionario) foi usada para acessar a camada correspondente em objetos de seus subtipos (Gerente e Comissionado). Assim, objetos de tipos diferentes são acionados por um mesmo conjunto de métodos, ou seja, recebem os mesmos tipos de mensagens. Esta situação é ilustrada na figura que segue.

    Quando o tipo de referência usada não é compatível com a hierarquia do objeto, o compilador sinaliza o erro e impede que o código seja compilado, evitando que objetos sejam incorretamente manipulados por referências inadequadas.

    Já referências de tipo Object, por meio do upcasting, podem acessar qualquer tipo de objeto, pois qualquer tipo é subclasse de Object e todo objeto possui como camada mais interior aquela que corresponde a Object, como mostra a figura que segue.

    Assim temos que é possível fazer:

    Object o1 = new Gerente(2367);
    Object o2 = new Comissionado(3489);
    Object o3 = f2;
    Object o4 = f5;
    Object o5 = new String("Upcasting");

    Isto significa que, sob certos aspectos, uma operação de up type casting é como uma operação implícita de coerção (conversão de tipo) que possibilita o uso mais genérico de um objeto, mas só é válida quando utiliza subclasse do tipo real do objeto.

    O upcasting é um mecanismo polimórfico de generalização, que permite o uso de um objeto por meio de referências de seus supertipos, embora as operações estejam limitadas as características da superclasse usada. Isto é conveniente para tratar famílias de objetos, pois uma referência de superclasse permite manipular qualquer um de seus subtipos, ao invés de requerer referências de tipos específicos.

    Isto flexibiliza o armazenamento de objetos em arrays e outras estruturas de dados; possibilita que métodos retornem objetos de tipos diferentes (mas de uma mesma família; além de facilitar alterações no projeto de sistemas.

    Por exemplo, no fragmento de código que segue, um array de tipo Funcionario pode conter objetos de qualquer tipo derivado de Funcionario:

    // cria array de Funcionario
    Funcionario folha[] = new Funcionario[3];

    // instancia e ajusta objeto tipo funcionário
    Funcionario f = new Funcionario(1234);
    f.setSalarioBase(850);

    // atribui objeto Funcionario ao array de mesmo tipo
    folha[0] = f;

    // instancia e ajusta objeto tipo Comissionado
    Comissionado c = new Comissionado(2345);
    f.setSalarioBase(650);

    // atribui objeto Comissionado ao array
    folha[1] = c;

    // instancia objeto Gerente, atribuindo-o ao array, ajustando-o
    folha[2] = new Gerente(3456);
    folha[2].setSalarioBase(4500);

    Nos elementos [1] e [2] do array, cujo tipo declarado é Funcionario, ocorre o upcasting, pois objetos de subclasses de Funcionario são ali armazenados.

    O uso do array de Funcionario permite generalizar o tratamento de funcionários de tipos diferentes, como no outro fragmento que segue:

    double folhaBaseTotal = 0;
    for(int i=0; i<folha.length; i++) {
       folhaBaseTotal += folha[i].getSalarioBase();
    }
    System.out.println("Folha total = " + folhaBaseTotal);

    No laço existente, a despeito do tipo real, todas as características do tipo Funcionario estão disponíveis por meio das referências existentes no array de tipo Funcionario. No entanto, ao acionar método getSalarioBase(), disponível na interface do tipo Funcionario, é de fato acionada a implementação existente na classe efetiva do objeto armazenado naquela posição, pois esta substitui (method overriding) a definida em Funcionario. Assim, este laço de tratamento generalizado acaba usado a operação definida nas subclasses de Funcionario, sem qualquer tratamento condicional.

    O upcasting permite, assim, criar operações genéricas para lidar com múltiplos tipos de objetos, relacionados numa hierarquia de classes por meio de um ancestral comum, como segue:

    double getFolhaBaseTotal(Funcionario[] folha ){
       double folhaBaseTotal = 0;
       for(int i=0; i<folha.length; i++) {
          folhaBaseTotal += folha[i].getSalarioBase();
       }
       return folhaBaseTotal;
    }

    O método getFolhaBaseTotal(Funcionario[]) recebe um array de tipo Funcionario. Como antes, a despeito do tipo real, todas as características do tipo Funcionario estão disponíveis por meio das referências existentes no array de tipo Funcionario. Mas, dada a sobreposição de métodos, serão usadas as implementações dos tipos específicos. Então, mesmo que surjam novos subtipos derivados de Funcionario, esta operação continuará a funcionar, sem necessidade de qualquer alteração.

    Polimorfismo e downcasting

    O polimorfismo também dispõe de uma operação que permite converter uma referência de superclasse em outra de subclasse, portanto mais especializada, que é chamada de down type casting, downcasting ou coerção para subtipo.


    Como mostra a figura, o downcasting é uma operação inversa ao upcasting. Enquanto o upcasting ocorre quando se utiliza uma referência de supertipo (ancestral) para o tratamento de um objeto; no downcasting é usada uma referência de subtipo (descendente) para o tratamento de um objeto.

    Aqui, a referência desejada como resultado do downcasting passa a utilizar uma camada mais externa do objeto, decorrente da especialização. Mas como o compilador não pode garantir que o objeto referenciado possua tais camadas adicionais, esta operação deve ser explicitamente indicada pelo programador, como uma espécie de autorização para sua realização.

    Observe no fragmento de código que segue, como uma referência f3, de tipo Funcionario ou mesmo Object, é transformada em um objeto de tipo Comissionado (que é uma de subclasses); ou como uma referência de Object é transformada em outra do tipo String:

    Comissionado c = (Comissionado) f3;
    String str = (String) obj;

    A transformação de tipo, down type casting ou coerção possibilita transformar uma referência do objeto em outra de seu tipo real ou de tipo mais próximo. Como nem sempre isso será possível, porque o objeto não foi, de fato, criado como algo daquele tipo, a compilação do código requer a explicitação da conversão por meio da indicação do tipo de destino entre parêntesis.

    Se a referência não pode ser transformada na tipo indicado, o downcasting provoca o lançamento da exceção ClassCastException, como nestes exemplos inválidos:

    // Upcasting OK
    Funcionario f4 = new Gerente (6789);
    // Downcasting INVALIDO
    Comissionado c = (Comissionado) f4;

    Para evitar esse tipo de exceção, existe o operador instanceof.

    Operador instanceof

    O operador especial instanceof, que retorna um resultado booleano, permite verificar, em tempo de execução, se um objeto é de um tipo específico ou de suas subclasses. A sua sintaxe é:

    <objeto> instanceof <Tipo>

    O uso deste operador retorna true, quando o objeto é uma instância direta do tipo indicado ou de alguma de suas subclasses; ou false, quando o objeto não é uma instancia do tipo indicado ou de qualquer uma de suas subclasses.

    O uso do operador instanceof permite evitar erros em operações de downcasting, mas sua aplicação não pode ser generalizada, devendo empregar tipos específicos. Um exemplo:

    Funcionario func = new Gerente(5678);
    if (func instanceof Gerente) {
       Gerente g = (Gerente) func; // downcasting
       System.out.println("Gerente depto: " + g.getGerencia() );
    }

    Outro exemplo:

    Integer n = new Integer (123456789);
    if (n instanceof Number) {
       count= n.intValue();
    }

    Com o uso de instanceof é possível garantir a execução de operações de downcasting sem a ocorrência de exceções ClassCastException decorrentes de coerção inválida de objetos.

    Considerações finais

    O polimorfismo é o mecanismo principal da OO que pode se manifestar tanto em tempo de compilação, quanto em tempo de execução.

    Em tempo de compilação o polimorfismo aparece como a sobrecarga de métodos (method overload), também como sobrecarga de operadores em algumas linguagens OO, também sendo chamado de ligação precoce (early binding). Já em tempo de execução, aparece como sobreposição de métodos (method overriding), que é decorrência do uso do mecanismo da herança onde a versão mais especializada de um método, presente em subclasses, é acionada, situação conhecida como ligação tardia (late binding).

    Assim, com o polimorfismo é possível:
    • Que subclasses forneçam implementações diferentes de métodos com uma mesma assinatura (ou seja, a sobreposição de métodos ou method overriding), provendo então comportamentos distintos, modificados em cada classe derivada conforme a especialização desejada.
    • Que referências de superclasse acionem métodos de subclasse comuns à superclasse (sobrepostos) em tempo de execução, ou seja, invoquem métodos diferentes, mas com a mesma assinatura, possibilitando que tipos diferentes recebam as mesmas mensagens.

    Finalmente, grande parte da flexibilidade e elegância das linguagens de programação OO decorrem do polimorfismo.

    POO-P-23-Polimorfismo
    POO-A-25-Classes internas e anônimas

    Referências Bibliográficas

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

    segunda-feira, 26 de março de 2018

    POO::Plena-23-Polimorfismo

    POO-P-22-Herança MúltiplaPOO-A-24-Polimorfismo, upcasting e downcasting
    A palavra polimorfismo vem do grego poli morfos e significa muitas formas. É a característica chave da orientação a objetos que torna possível utilizar múltiplas formas de construção de objetos. Na OO o polimorfismo de manifesta na sobrecarga, na herança, na sobreposição e também na generalização.

    Na verdade, o polimorfismo é um mecanismo único, semelhante a uma força. Em uma direção ele provê a especialização (de tipos), enquanto no sentido oposto oferece a generalização (de tipos). É por meio do polimorfismo que as linguagens OO proporcionam mecanismos de classificação e também de reuso.

    Mecanismos do polimorfismo::sobrecarga

    A sobrecarga de métodos (method overload) é uma propriedade da OO que permite a existência de dois ou mais métodos com o mesmo nome em uma mesma classe, desde que possuam assinaturas diferentes. Devemos recordar que a assinatura de um método é a combinação de seu nome com a lista dos tipos de seus parâmetros formais. 

    Assim, o método estático pow da classe Math, que toma dois parâmetros do tipo double, tem assinatura pow(double, double); o método getClass() da classe Objeto não toma parâmetros, sendo sua assinatura getClass(void); enquanto o método get da classe ArrayList toma um inteiro como argumento e tem assinatura get(int).

    É a assinatura diferente que possibilita a diferenciação do método que deve ser acionado no ponto de sua invocação (i.e., na chamada do método); por isso o tipo de retorno é desconsiderado e não faz parte da assinatura, como ilustra a figura que segue.


    Desta maneira, métodos conceitualmente semelhantes podem possuir o mesmo nome, diferenciando-se pela lista requerida de argumentos, que possibilita que cada um realize sua operação de maneiras distintas. Por exemplo, a classe String oferece quatro métodos com nome indexOf:
    • indexOf(char) retorna a posição onde se localiza a primeira ocorrência do caractere fornecido como argumento;
    • indexOf(char, int) retorna a posição da primeira ocorrência do caractere suprido como argumento, a partir da posição indicada;
    • indexOf(String) retorna a posição onde se encontra a primeira ocorrência da substring indicada como argumento;
    • indexOf(String, int) retorna a posição da primeira ocorrência da substring dada como argumento, a partir da posição indicada.

    A existência de métodos sobrecarregados proporciona flexibilidade para o uso da classe, pois várias formas das operações estão presentes, ao mesmo tempo que mantém a interface da classe simples, porque conceitualmente os métodos sobrecarregados fornecem uma mesma operação.

    Construtores também podem ser sobrecarregados, aumentando as possibilidades de criação de objetos. A classe String possui 11 diferentes construtores, que permitem várias formas de instanciação de cadeias de caracteres.

    Mecanismos do polimorfismo::herança

    A herança (inheritance) é, talvez, a segunda característica mais importante da OO. É o mecanismo que permite a construção de novos tipos de dados (classes) baseada em outros tipos já existentes, possibilitando a criação de hierarquias de classes, como ilustrado a seguir.

    Tipicamente, uma hierarquia de classes é como uma árvore cuja raiz é colocada na parte superior do diagrama. A classe-raiz, que dá origem às demais, é colocada no topo, assim, de modo que, para qualquer classe presente na árvore, seus tipos ascendentes ficam acima, enquanto seus descendentes ficam abaixo.

    Na herança, uma classe, tomada como superclasse ou classe-pai, pode dar origem a uma ou mais novas classes denominadas de subclasses ou classes-filha. Esta relação pai-filha, identificada pela resposta afirmativa a questão "é um? | é do tipo?", conecta as classes no diagrama e permite, do ponto de vista de implementação, que:
    • as características ancestrais sejam compartilhadas pelos descendentes;
    • novas características possam ser adicionadas nos descendentes;
    • características presentes nos ascendentes sejam modificadas ou restringidas.

    O compartilhamento da implementação da superclasse nas subclasse já é um ganho, pois atributos e operações públicas e protegidas tornam-se acessíveis para todos os descendentes da classe, evitando repetição de código e facilitando a manutenção. Isto é um mecanismo efetivo de simplificação de projeto e de reuso.

    Além disso, ao adicionar novos atributos e operações, a subclasse é diferenciada de seu ancestral, o que corresponde a um mecanismo de extensão ou de especialização das classes.

    Também é possível modificar o comportamento de operações existentes, utilizando outro mecanismo decorrente do polimorfismo denominado sobreposição, que será comentado um pouco mais a frente.

    A adição de novos atributos e novas operações é a forma mais comum de aproveitar o mecanismo da herança. Nesta situação procura-se identificar os elementos comuns a um conjunto de classes, mantendo tais  elementos comuns em uma classe que funcionará como ponto de partida comum para um conjunto de outras classes, tal como na ilustração acima, onde a classe Funcionario concentra aspectos comuns de todos os tipos de funcionário de uma empresa, possibilitando que novas classes, especializadas em funções específicas, como Caixa, Motorista, Segurança ou Gerente, possam ser criadas.

    Para auxiliar no tarefa de fatoração das características comuns e também na identificação daquelas que são específicas, a organização de uma tabela de classes/tipos e suas funcionalidades pode ser muito útil. Partindo da narrativa descritiva do problema, as classes que representam as entidades/atores do problema usualmente figuram como substantivos. Outros substantivos, adjetivos e complementos costumam indicar os atributos destas entidades; já os verbos apontam as funcionalidades que serão representadas por métodos das classes associadas.

    É claro que o benefício da herança é permitir o compartilhamento da implementação dos aspectos comuns destes funcionários, sem necessidade de repetição deste código. Isto mantém a coesão necessária a cada classe, ao mesmo tempo que promove o reuso, facilita a manutenção do código e ainda permite que, no futuro, novas classes sejam adicionadas à hierarquia construída.

    Mecanismos do polimorfismo::sobreposição

    A sobreposição (ou substituição) de métodos, também conhecida como method overriding, consiste na implementação de um método na subclasse com a mesma assinatura de outro existente na superclasse.

    Isto permite dotar a subclasse de uma implementação distinta daquela existente na superclasse, mas mantendo sua interface, o que facilita seu uso por meio do polimorfismo, como será visto mais à frente.

    É necessário frisar que a sobreposição envolve a criação de um novo método, com a mesma assinatura, de uma operação existente nas superclasses daquela onde é codificado. Assim, isto constitui um mecanismo distinto da sobrecarga, que envolve a criação de métodos com mesmo nome, mas assinaturas diferentes, em uma mesma classe.

    Em conjunto com a herança, a sobreposição permite que uma subclasse tenha comportamento diferente da superclasse, mantendo sua interface. Esta é uma outra forma de especialização da classe, sem ampliação de suas características.

    Além disso, métodos considerados inadequados na subclasse, embora não possam ser ocultos (ou seja, deixarem de ser públicos), suas versões sobrepostas podem lançar exceções impedindo seu uso. Com isso, a sobreposição permite a contração de classes, ou seja, a redução de suas funcionalidades.

    Mecanismo da sobrecarga::generalização

    Sempre é adequado destacar que o polimorfismo é a característica mais importante da OO, pois representa o mecanismo pelo qual vários tipos de objetos, ou seja, várias formas de implementação, podem ser tratadas como sendo de um tipo comum.

    Por isso é importante observar que:
    • Uma referência (de instância) é uma variável cujo tipo é uma classe.
    • Cada referência aponta para UM objeto de cada vez.
    • É possível trocar o valor de uma referência, por outra, para que ela aponte para OUTRO objeto.
    • O valor null indica que a referência não aponta qualquer objeto válido.
    • Várias referências diferentes podem apontar para um mesmo objeto.

    No Java, em particular, as referências não são endereços de memória, mas identificadores de objetos definidos pela máquina virtual Java (JVM). Por meio desses identificadores, a JVM verifica quando os objetos estão sendo referenciados (em uso aparente). Se a JVM detecta que um objeto deixa de ser referenciado, isto é, quando não existem referências apontando para um objeto específico, ele é marcado pelo Automatic Garbage Collector para descarte (remoção da memória).

    Também sabemos que na plataforma Java, qualquer classe é, direta ou indiretamente, uma subclasse de java.lang.Object, pois mesmo numa declaração de classe simples, implicitamente usamos o mecanismo da herança, tomando como superclasse o tipo Object, que constitui assim a raiz da hierarquia de classes Java. Com o C# ocorre o mesmo, mas com a classe System.Object sendo a raiz da hierarquia de classes.

    Sendo assim, considerando qualquer hierarquia de classes, toda classe existente é uma subclasse de Object, de modo que uma variável do tipo Object pode armazenar referências de qualquer tipo de objeto:

    Object o;
    o = new String("Polimorfismo");
    o = new Integer(123456);
    o = new ArrayList();
    o = new FileReader("arquivoTexto.txt");
    o = new JFrame("GUI");
    o = new Thread("fluxo de processamento");

    O polimorfismo permite que um objeto seja, transparentemente, tratado como qualquer outro tipo ascendente (superclasses da qual é derivado), e também como sendo do seu tipo verdadeiro, isto é, como foi instanciado de fato.

    A possibilidade de admitir múltiplos tratamentos para um mesmo objeto é a própria definição de polimorfismo. Mas existe uma condição envolvida nesta forma do polimorfismo: um objeto pode tratado como do seu próprio tipo, como sendo de qualquer um de seus ancestrais, até a raiz da hierarquia, ou seja, como do tipo Object.

    Se observarmos novamente a figura da hierarquia de funcionários, podemos concluir que as classes Caixa, Motorista, Segurança e Gerente, derivadas diretamente da classe Funcionario, podem ter seus objetos tratados como sendo do tipo Funcionario, ou mesmo Object.

    Isso possibilita a generalização, ou seja, uma operação que faz o contrário da herança, que permite a especialização.

    Um objeto cuja classe é derivada de outra é como uma composição de objetos em camadas. A camada mais externa corresponde ao tipo real do objeto (por exemplo Gerente) e nela se encontram as adições providas por esta classe (sua especialização em relação à camada anterior). A camada mais interna é sempre aquela que corresponde ao tipo Object, onde estão presentes seus elementos. As camadas intermediárias são aquelas providas pelos descendentes de Object até a camada mais externa.

    A figura que segue ilustra um objeto de uma possível classe Comissionado, a qual é derivada de (uma subclasse de) Funcionario, que por sua vez é derivada de (uma subclasse de) Object. Assim, o objeto possui três camadas, uma provida por cada classe de sua hierarquia, a mais interna correspondente a Object e a mais externa correspondente ao seu tipo real (ao tipo de sua criação -- instanciação).


    Conforme o tipo da referência usada para acessar um objeto, determina-se quais camadas serão efetivamente acessadas. Se é utilizada uma referência do tipo real (de instanciação), temos acesso a todas os membros públicos deste tipo e de todos os tipos ancestrais (ascendentes). Na figura acima, a referência do tipo Comissionado pode acessar todos os membros públicos do objeto presentes na camada mais externa (Comissionado), na camada intermediária (Funcionario) e também na camada mais interna (Object).

    Mas se a referência é do tipo Funcionario, só é possível acessar os membros desta camada (Funcionario) e das mais internas (no caso apenas Object). Uma referência de tipo Funcionário não consegue acessar nada presente em camadas mais externas do que seu tipo, porque sua classe não prevê sua existência, pois foram adicionadas em especializações (subclasses) posteriores.

    Da mesma maneira, uma referência do tipo Object só consegue acessar a porção referente a este tipo presente em objetos de qualquer classe.

    Por conta da construção dos objetos em múltiplas camadas e do comportamento dos diferentes tipos de referências em relação às camadas presentes nos objetos, temos duas importantes operações decorrentes do polimorfismo:
    • up type casting (ou apenas upcasting) e
    • down type casting (ou apenas downcasting).
    Mas este é o assunto do próximo post!

    POO-P-22-Herança MúltiplaPOO-A-24-Polimorfismo, upcasting e downcasting

    Referências Bibliográficas

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