PROGRAMAÇÃO ORIENTADA A PROCEDIMENTOS Paradigma de software mais popular na década de 70 Concentra-se em subprogramas e bibliotecas de subprogramas Computações: os dados são enviados aos subprogramas Exemplo: um array é passado como parâmetro a um subprograma de classificação PROGRAMAÇÃO ORIENTADA A DADOS (OBJETOS) "Linguagens baseadas em dados" Raízes: Simula67 Smalltalk80 - considerada a única linguagem puramente OO Tipos de dados abstratos Herança Polimorfismo PROGRAMAÇÃO ORIENTADA A DADOS (OBJETOS) II Computações realizam-se do seguinte modo: sobre um objeto-dados é invocado um método associado a este objeto-dados Exemplo: o processo de classificação de um array é ativado invocando-se este método no objeto específico HERANÇA É o centro da programação orientada a objetos Na década de 80 tornou-se patente aos desenvolvedores que a melhor forma de aumentar a produtividade era a reutilização do software Os tipos abstratos de dados eram as unidades a serem reutilizadas HERANÇA II A reutilização origina duas questões: Quase sempre, os recursos do tipo já existente não são adequados ao novo emprego, e o modificador precisaria entender todo o código existente e alterar os clientes Como definidos até aqui, os tipos de dados abstratos são independentes e estão no mesmo nível, o que torna impossível estruturar um problema A herança fornece solução para as duas questões acima HERANÇA - solução para o problema da modificação Um novo tipo de dados abstrato pode herdar dados e funcionalidades de um tipo existente Também é permitido modificar algumas entidades e adicionar novas Exemplo: suponhamos que o tipo array, além de ter um método de classificação, necessite agora de método para computar sua mediana Em vez de modificar a implementação do tipo existente, pode-se definir uma subclasse que retém a classificação e adiciona a si a computação da mediana NOMENCLATURA Uma classe definida pela herança de outra é chamada classe derivada ou subclasse A classe da qual a nova é derivada chama-se classepai ou superclasse ou classe-raiz As chamadas a métodos frequentemente são chamadas de mensagens A coleção de métodos de um objeto é o protocolo de mensagem ou interface de mensagem do objeto Toda mensagem tem duas partes: o objeto específico ao qual ela está sendo enviada e o nome de um método que define a ação solicitada no objeto Computações podem ser vistas como mensagens AS CLASSES DERIVADAS COMO CLIENTES O acesso a um membro de uma classe pode ser "concedido" ou "reusado" Uma terceira categoria de acesso, com frequência chamada protected, é usada para fornecer concessão a uma classe derivada SOBREPOSIÇÃO DE MÉTODOS Um método modificado numa classe derivada tem o mesmo nome e muitas vezes o mesmo protocolo do método original O novo método fica sobreposto ao original Objetivo da sobreposição: fornecer operação específica a objetos da classe derivada, que não é apropriada para objetos da classe-pai Exemplo: classe raiz French_Gothic com método draw, classes derivadas Reims, Amien e Chartres com métodos draw herdados e sobrepostos ao da classe raiz HERANÇA SIMPLES E HERANÇA MÚLTIPLA Se a classe derivada possui uma única classepai, o processo chama-se herança simples Caso contrário (duas ou mais classes-pai), chama-se herança múltipla "Hierarquia de classes" = conjunto de heranças em sua totalidade Hierarquia de heranças simples gera uma árvore de derivação Hierarquia de heranças múltiplas gera um grafo (direcionado) de dependências HERANÇA SIMPLES E HERANÇA MÚLTIPLA II Exemplo: um applet do Java com animação os applets são suportados pela classe Applet a concorrência (necessária para a animação) é suportada pela classe Thread é necessário que um applet herde de ambas as classes HERANÇA SIMPLES E HERANÇA MÚLTIPLA III Desvantagem da hierarquia: dependência entre as classes (os tipos de dados abstratos perdem sua independência...) Porém: é impossível reusar tipos sem criar dependências entre alguns deles HERANÇA SIMPLES E HERANÇA MÚLTIPLA IV Problema da colisão de nomes: se a subclasse C herdar tanto de A como de B, e ambas incluem a variável hereditária sum, como C poderá referir-se às duas sum diferentes? Problema da herança diamante: suponha também que A e B sejam subclasses de Z Não está claro ainda se os benefícios da herança múltipla valem o esforço adicional de gerenciar o intrincado complexo de dependências... HERANÇA EM C++: Exemplo class classe_base{ private: int a; float x; protected: int b; float y; public: int c; float z; }; class subclasse_1: public classe_base{...} class subclasse_2: private classe_base{...} DISCUSSÃO DO EXEMPLO ANTERIOR Observe a existência de um modo de acesso na definição das subclasses É claro que a,x não são visíveis em qualquer descendente de classe_base subclasse_1 mantém b,y como protegidos e c,z como públicos Em subclasse_2 b,y,c,z são todos membros privados Nenhuma classe derivada de subclasse_2 pode ter acesso a qualquer membro de dados de classe_base Isto é, uma subclasse private X interrompe o acesso de suas descendentes aos membros de dados dos ancestrais de X HERANÇA EM C++: outro exemplo class lista_encadeada{ class no{ friend class lista_encadeada; private: int info; no *prox; } private: no *inicio; public: lista_encadeada ( ) {inicio=0}; void insere_inicio(int); void insere_final(int); int remove_inicio( ); int vazia( ); }; HERANÇA EM C++: outro exemplo II class pilha : public lista_encadeada{ public: pilha( ) { } void push (int elem){ lista_encadeada::insere_inicio(int elem); } void pop ( ) { return lista_encadeada::remove_inicio( ); } }; HERANÇA EM C++: outro exemplo III class fila : public lista_encadeada{ public: fila( ) { } void enqueue (int elem){ lista_encadeada::insere_final(int elem); } int dequeue ( ) { return lista_encadeada::remove_inicio( ); } }; HERANÇA MÚLTIPLA EM C++ class A {...} class B {...} class C: public A, public B {...} :: -> operador de resolução de escopo (solução C++ para o conflito de nomes em herança múltipla ) REEXPORTAÇÃO class subclasse_3 : private classe_base{ classe_base :: c; // c passa a ser visível em // descendentes de //subclasse_3 ...} :: -> operador de resolução de escopo POLIMORFISMO E VINCULAÇÃO DINÂMICA Variáveis polimórficas: são do tipo da classe-pai e também podem referenciar objetos de qualquer uma das subclasses da mesma Variáveis polimórficas são ponteiros ou referências Considere um método M sobreposto nas subclasses Quando M é invocado por uma variável polimórfica, esta chamada é vinculada dinamicamente ao método da classe apropriada Isto permite a extensão e a manutenção de sistemas com muito mais facilidade POLIMORFISMO E VINCULAÇÃO DINÂMICA II Exemplo: variável polimórfica cathedral do tipo French_Gothic pode referenciar os métodos draw de Reims, Amien e Chartres. Quando cathedraw for usada para chamar draw, esta chamada será vinculada dinamicamente à versão correta de draw O MESMO EXEMPLO EM C... Se o exemplo das catedrais fosse escrito em C, os três tipos de catedrais poderiam estar um struct Haveria uma única função draw que usaria uma instrução switch, do seguinte modo: um parâmetro por valor seria enviado a draw para especificar qual item do switch contém as diretivas de desenho corretas Se adicionarmos uma nova catedral do tipo Paris, temos que modificar o switch e repensar o protocolo de draw Com orientação a objetos, basta criar uma nova subclasse com seu método sobreposto. Não há necessidade de alterar o código existente QUANDO A HIERARQUIA CRESCE... Às vezes a classe-raiz está tão alta e tão distante na hierarquia que nem faz sentido instanciá-la num objeto particular Exemplo: suponha que Building seja classe-pai de French_Gothic. Building é um tipo "edifício genérico" Talvez não faça sentido um método draw em Building Mas como todas as suas classes descendentes tem este método implementado, o protocolo (mas não o corpo) deste método é incluído em Building Este método abstrato é chamado método virtual A classe Building é chamada classe virtual - não pode ser instanciada, porque nem todos os seus métodos POLIMORFISMO EM C++ class Building { // classe "abstrata" (virtual) public: virtual void draw( ) = 0; // função virtual "pura" ...} class French_Gothic : public Building { public: virtual void draw( ) {...} // método sobreposto ...} class German_Gothic : public Building { public: virtual void draw( ) {...} // método sobreposto ...} POLIMORFISMO EM C++ II French_Gothic x; German_Gothic y; Building &ref_build = x; // ref_build é polimórfica // e faz referência a uma // catedral francesa ref_build.draw( ); y.draw( ); // vinculado dinamicamente ao draw // para catedral francesa // vinculado estaticamente ao draw // para catedral alemã É claro que a vinculação estática, apesar de menos flexível, é mais eficiente que a dinâmica POLIMORFISMO E VERIFICAÇÃO DE TIPOS Quando o método sobreposto chamado é vinculado ao método correto, deve-se checar se: os tipos dos parâmetros reais na mensagem coincidem com os parâmetros formais do método correto o tipo do valor de retorno do método correto é o tipo esperado da mensagem Se a vinculação for dinâmica, a verificação deverá ser retardada até o ponto da execução em que a mensagem é enviada MODELO DE COMPUTAÇÃO PURAMENTE OO É uma técnica uniforme: enviar uma mensagem a um objeto para invocar um de seus métodos A resposta à mensagem é um objeto que retorna o valor da computação do método Os objetos podem ser vistos como uma coleção de computadores que se comunicam entre si pelas mensagens (modelo abstrato de computação distribuída) MODELO DE COMPUTAÇÃO PURAMENTE OO II Cada objeto é a abstração de um computador com seus dados (sua memória local) e sua capacidade de processamento (seus métodos) Podemos então simular o problema real identificando os objetos do mundo real, seus processos e as comunicações necessárias entre eles MODELO DE COMPUTAÇÃO PURAMENTE OO III Tudo é objeto - desde o menor número inteiro até um sistema complexo de dados e processos Todos os tipos são classes Toda a computação é a invocação de um método. Exemplo: 21 + 2 Não há distinção entre classes predefinidas e classes definidas pelo usuário - todas são tratadas da mesma maneira MODELO DE COMPUTAÇÃO PURAMENTE OO IV A vantagem é a uniformidade, a elegância, a maleabilidade do uso Desvantagem: tudo deve ser feito pelo processo de passagem de mensagens, o que torna as operações mais lentas em relação às suas similares imperativas Smalltalk: chega a ser comercialmente inviável pela sua baixíssima eficiência ALTERNATIVAS À COMPUTAÇÃO PURAMENTE OO Alternativa I: manter um modelo completo de tipificação imperativo e simplesmente adicionar o modelo OO Porém: a estrutura de tipos fica extremamente confusa (C++...) Alternativa II: manter o modelo imperativo apenas para tipos primitivos escalares Necessidade das classes wrapper (envoltórias) sobre tipos não-objeto (Java...) SUBCLASSES SÃO SUBTIPOS? Exemplo: Ada subtype SMALL_INT is INTEGER range -100..100 Para todos os efeitos, SMALL_INT é uma variável INTEGER Usa as mesmas operações e pode aparecer no lugar de qualquer variável INTEGER Uma classe é chamada subtipo se guardar as mesmas características acima com sua classepai SUBCLASSES SÃO SUBTIPOS? II Para ser subtipo, a subclasse deve: herdar "tudo" adicionar/sobrepor métodos de modo "compatível" (sem causar erros de tipo) a sobreposição segura pode ser garantida usando exatamente o mesmo protocolo mas há restrições menos severas... OCULTAÇÃO DA INFORMAÇÃO ÀS SUBCLASSES A implementação da classe fica oculta aos clientes E com relação às subclasses? Herança de interface = apenas a interface da classe pai é visível à subclasse Herança de implementação = os detalhes da implementação também são visíveis na subclasse OCULTAÇÃO DA INFORMAÇÃO ÀS SUBCLASSES II Exemplo: subclasse da classe pilha que deve ter um método para retornar o elemento logo abaixo do topo com herança de implementação, basta verificar ptr_pilha [top_ptr-1] com herança de interface: int segundo( ){ ... int temp = top( ); pop( ); int result_temp = top( ); push(temp); return result_temp; } COMENTÁRIOS GERAIS AO C++ Compatibilidade quase completa com C (questão de projeto) Híbrido no sentido de que possui tanto os tipos das linguagens imperativas tradicionais como a estrutura de classes da OO Suporta tanto programação orientada a procedimentos como a objetos COMENTÁRIOS GERAIS AO C++ II Objetos podem ser alocados nos mesmos lugares onde se aloca variáveis no C Objetos dinâmicos devem ser explicitamente desalocados com delete Não há reivindicação de armazenamento implícita C++ não está imune ao problema das referências oscilantes e do vazamento de memória... Com a desvantagem de que os ponteiros agora são para objetos! COMENTÁRIOS GERAIS AO C++ III Todas as classes incluem pelo menos um método construtor, utilizado para inicializar os membros de dados Métodos construtores são chamados implicitamente na criação do objeto Se nenhum construtor é incluído na definição da classe, o compilador incluirá um método construtor trivial COMENTÁRIOS GERAIS AO C++ IV O destrutor é implicitamente chamado quando o objeto deixa de existir Desalocação de membros de dados dinâmicos devem estar presentes no destrutor! Herança do C++ é confusa em relação ao Smalltalk em termos de controle de acesso Levando também em consideração as declarações friend, o programador C++ tem um controle altamente detalhado (e complicado!) do acesso aos membros Questiona-se se isto é produtivo... COMENTÁRIOS GERAIS AO C++ V Em C++ o programador pode especificar vinculação estática ou dinâmica A vinculação dinâmica em C++, apesar de mais lenta que a estática, tem um acréscimo de custo fixo, mesmo que a mensagem seja vinculada a um método distante na hierarquia. Em Smalltalk, as mensagens são sempre vinculadas dinamicamente a métodos e o custo da vinculação depende da distância na hierarquia COMENTÁRIOS GERAIS AO C++ VI A possibilidade de verificação estática de tipos em C++ é vantajosa As classes genéricas possibilitam verificação estática Em Smalltalk, toda verificação é dinâmica. Pode-se até chamar métodos não existentes, e isto só será notado na execução COMENTÁRIOS GERAIS AO C++ VII Smalltalk é totalmente dedicada ao paradigma OO, ignorando imposições de bases guerreiras de usuários Smalltalk é uma linguagem pequena, elegante, altamente simples e ortogonal C++ é uma linguagem grande, complexa e sem filosofia particular como base, exceto a de incluir usuários de C COMENTÁRIOS GERAIS AO C++ VIII Alguns acham que os recursos de C++ nem sempre se encaixam bem sobre o C Testes revelam que Smalltalk compila a 10% da velocidade de um compilador C otimizado C++ compila exigindo somente um pouco mais de tempo que os compiladores C Dada a grande distância de eficiência entre C++ e Smalltalk, é fácil explicar a diferença de sucesso comercial entre as duas Além disso, há a "compatibilidade" de C++ com a base usuária C