Estudo das Diferenças entre a Herança por Delegação e a Herança por Concatenação Maria José Angélico Gonçalves
[email protected] Maio de 2007 08T151A011 - Tecnologías de Objetos Curso de Doutoramento: Engenharia de SW basada en Componentes Reutilizables, Aplicaciones en Interfaces Hombre-Maquina
1. Introdução A herança é considerada, geralmente, a característica que distingue a programação orientada a objectos de outros paradigmas de programação mais recentes. Embora, este mecanismo tenha sido amplamente estudado pela comunidade científica, ainda é um tema bastante controverso. Os investigadores tendem a discordar relativamente ao seu significado e ao seu uso (Taivalsaari, 1996). “Herança é um mecanismo de modificação incremental na presença de latebound self-reference” Adaptado de Cook (1989) e Cook and Palsberg (1989) citado em Taivalsaari,(1996)
Geralmente late-binding1, self-reference2 e super-reference3 estão presentes em todos os mecanismos de herança. Late-binding e self-reference permitem que o programador execute a “cirurgia” que pode mudar o comportamento do objecto sem o alterar fisicamente. Super-reference adiciona a estas potencialidades a permissão do acesso às propriedades redefinidas, reduzindo desse modo a necessidade da duplicação do código. A combinação correcta destas potencialidades permite que as propriedades dos objectos se reutilizem sem existência de cópia. Esta característica é a principal vantagem a outros estilos e técnicas de programação (Cook, 1989). Neste sentido, a herança é um mecanismo fundamental para a construção de programas. É, frequentemente, referenciada na bibliografia como uma solução de problemas no desenvolvimento de software. Veio trazer muitos benefícios à programação, tais como: modelação, simplificação e reutilização. Neste trabalho referem-se, em primeiro lugar, os aspectos principais das linguagens de programação orientadas a objectos baseadas em classes (POO baseada em classes) e das linguagens de programação orientada a objectos baseadas em protótipos (POO baseada em protótipos). 1 É uma associação feita pelo compilador/interpretador entre um atributo e uma entidade que ocorre durante o tempo de execução ou muda durante a execução do programa. 2 A designação depende da linguagem de programação. Para o programador, self-reference aparece tipicamente na forma de uma pseudovariavel especial, self. Pode ser usada explicitamente para informar o sistema que está pedido late-binding via selfreference. 3 São classes cujas instâncias também são classes. Dependendo da linguagem de programação utilizada existe/não existe suporte implícito/explicito.
1
Em segundo lugar, é definido o conceito de herança, efectuada uma breve síntese da sua evolução, e são mencionadas aplicações desse conceito, no contexto da sua utilização prática. Em seguida, são apresentadas duas formas básicas de implementação da herança: Delegação e Concatenação. São citadas algumas linguagens de POO que as aplicam e apresentadas as vantagens e desvantagens da delegação versus concatenação. Por último, é efectuado um estudo comparativo de três sistemas de programação orientados a objectos baseados em protótipos: Self, Kevo e Zero. Nos mesmos, são exemplificados os conceitos anteriormente apresentados e referidas as principais diferenças das linguagens. 2. Programação Orientada a Objectos (POO) baseada em Classes versus POO baseada em Protótipos Um objectivo importante que as linguagens de programação devem integrar, é a ortogonalidade, ou seja, a possibilidade de um conjunto relativamente pequeno de construções primitivas poder ser combinado com um número relativamente pequeno de maneiras para construir as estruturas de controlo e de dados da linguagem. A falta de ortogonalidade acarreta excepções às regras da linguagem. Praticamente todas as linguagens de programação orientadas a objectos são baseadas em classes (C++, Object Pascal, Java; Eiffel; Common Lisp; CorbaScript; Perl, etc.) ou baseadas em protótipos (Self; Ómega; Kevo; Poet/Mica, Cecil, JavaScript; MOO;NewtonScript, etc). A grande maioria baseia-se em classes (Morales, 2000; Taivalsaari A., 1995). A programação das linguagens baseadas em classes é obtida através da definição de classes (definição dos atributos e operações dos objectos dessa classe) e criação de hierarquias, nas quais propriedades comuns são transmitidas das superclasses para as subclasses através de mecanismos de herança, referida na bibliografia como class inheritance , standard inheritance ou, simplesmente, inheritance (Taivalsaari, 1995; Stein , 1989). Objectos destas classes são instanciados de forma que a execução do programa é vista como um conjunto de objectos relacionados que se comunicam enviando mensagens uns aos outros. As linguagens baseadas em protótipos não utilizam a noção formal de classe (ver figura 1), os objectos copiam-se de outros já existentes (Morales 2003, Borning 1986, Liberman 1986). A estrutura e comportamento de um conjunto de objectos não se define através de classes, define-se através de protótipos. O protótipo é, também, um objecto com uma estrutura, conteúdo e comportamento predefinido, que é utilizado para criar novos objectos mediante um processo de cópia, denominado cloning. A delegação, referida na bibliografia prototype inheritance, object inheritance or delegation (Taivalsaari, 1995) permite a partilha entre objectos e permite que um objecto possa alterar as suas respostas para pedidos de serviços em tempo de execução. Um protótipo pode ser pensado como, por exemplo, uma instância, que representa o comportamento de alguns conceitos. Conforme foi dito anteriormente os sistemas baseados em protótipos não apresentam classes, pelo que, não existe a noção de instância (Lieberman, 1986). O efeito de instanciação é a possibilidade de criar vários objectos com características similares, com a possibilidade de se modificarem individualmente, utilizando um processo de cópia.
2
Não existe um modelo standard de implementação de sistemas baseados em protótipos. Anteriormente, foram mencionadas várias linguagens de programação, que apresentam características muito diferentes. Porém, as mesmas podem ser divididas em duas categorias: linguagens baseadas em delegação e linguagens baseadas em cópia (cloning). Nas linguagens baseadas em delegação há um mecanismo especial “Delegation” que fornece a potencialidade incremental da modificação no desenvolvimento de software (Lieberman 1986).
Figura 1 – Criação de um objecto com classes e com protótipos Fonte: Morales (2003), Programación Orientada a Objetos: Clases versus Prototipos
“Delegação (delegation) é um mecanismo geral de partilha entre duas ou mais estruturas separadas. Este mecanismo requer a presença de late-bound e self refence, e opera no envio de mensagens de um objecto a outro sem alteração de Self-reference” (Taivalsaari 1995). Se um objecto “filho” não implementa uma determinada mensagem ele delega a mensagem para o objecto “pai”. Caso o objecto pai implemente aquela mensagem então ele executa-a com os dados do objecto “filho”, senão o objecto “pai” delega a mensagem nos seus “delegatee´s”. Pode existir uma lista de objectos para os quais as mensagens são delegadas. Esta situação causa conflito tal como na herança múltipla, referida em seguida. A solução é a existência de uma lista sequencial de objectos. Exemplo de sintaxe de uma linguagem por delegação (Taivalsaari,1995):
Var/ Method/Parent slotName [:-SlotContents] slotName – nome da nova variável Method, ou parent slot e slot contents (opcional) – o objecto será accionado por slotName.
3
Ungar, Chambres, Chang Holzle (1991), referiram que a linguagem Self pode incorporar tarefas realizadas em linguagens baseadas em classes. Esta linguagem diferencia dois tipos de objectos: Traits4 e prototypes5. Independentemente do sistema ser baseado em classes ou em protótipos existem duas formas básicas de implementação da herança: Delegação e Concatenação (Taivalsaari, 1996). 3. Herança
3.1 Definição do conceito Herança é um mecanismo que permite a uma linguagem de programação definir novos objectos a partir de objectos já existentes. Uma nova classe herda propriedades dos seus pais e pode introduzir novas propriedades que ampliam, modificam ou eliminam as propriedades herdadas. No contexto de programação, o conceito de herança foi introduzido pela linguagem SIMULA, em 1960, com a designação de concatenation (concatenação). Posteriormente, denominou-se suclassing , derivation (em C++), prefixing (em Simula e Beta), subtyping, generalization . Em 1990, Korson and McGregor (citados em Taivalsaari, 1996) caracterizaram a herança em duas vertentes diferentes: 1ª – Na Fase de desenho do programa de alto nível, a herança utiliza-se para modelar relacionamentos de generalização/especialização. 2ª – Na Fase de implementação de baixo nível de execução, a herança permite definir classes novas com base em classes já existentes. Investigadores, mais tarde, fazem uma distinção clara entre herança e subtyping. Herança, também designada implementação da herança, representação da herança ou subclasses, em sistemas baseados em classes, é um mecanismo de baixo nível que partilha ambiente e dados. Por outro lado subtyping, também designado especificação da herança ou herança de interface, expressa uma especialização conceptual. Este conceito foi aceite pela comunidade científica mas poucas linguagens de programação o integraram (POOL-I e Typed Smalltalk). Em 1991, LaLonde e Pugh (citados em Taivalsaari, 1996), propuseram as seguintes definições para os três conceitos: — Subclassing (subclasses) é um mecanismo da execução para compartilhar o código e representação6; —Subtyping é um relacionamento substituto — Is-a (É-um) é um relacionamento conceptual de especialização: descreve um tipo do objecto como um tipo especial de outro. Subclassing(subclasses), subtyping, e specialization(especialização) são importantes por razões diferentes. Subclasses suportam reutilização para implementar 4 Descrevem o ambiente do objecto. Correspondem às classes num sistema OO baseado em classes (Ungar, Chambres, chang Holzle, 1991) 5 Contém a informação. Correspondem às instâncias num sistema OO baseado em classes(Ungar, Chambres, chang Holzle, 1991) 6
Podemos
adicionar
um
comportamento
específico
(implementação)
generalização/especialização - Poliformismo
4
às
subclasses
de
uma
hirerarquia
de
bibliotecas de classes. Subtyping suporta reutilização para que o utilizador da biblioteca da classe possa saber que classes podem ser substituídas por outras classes. A Relação de Especialização (É-um ), por sua vez, é importante para compreender os relacionamentos lógicos entre os conceitos; neste sentido, a especialização é importante para o programador da biblioteca da classe. Brachman identificou vários tipos de relações É- um. Wegner distinguiu subtyptes (subset subtyping; Isomorphic copy subtyping; Object-oriented subtyping) de subtyping. Actualmente, grande parte dos sistemas orientados a objectos suporta herança múltipla. Ou seja, é possível que uma classe ou um objecto tenha mais que um parent (pai). Existem na bibliografia diferentes noções de herança (Cardelli, 1984; Cook, 1989; Wegner e Zdonik, 1988). De uma forma básica, herança pode ser definida como uma combinação de registos (Cardelli, 1984; Cook, 1989). Herança não é uma característica independente da linguagem de programação, porque interage com outros mecanismos da linguagem.
3.2 Aplicações da Herança Na prática, as linguagens orientadas a objectos utilizadas em aplicações comerciais raramente contemplam a herança de relacionamentos (specialization). Neste tipo de aplicações privilegia-se a economia de esforço na codificação, o espaço de armazenamento e a velocidade da execução (Smaltalk e C ++) . Conforme foi dito anteriormente, a herança pode ser vista sobre diferentes aspectos. Usos diferentes focalizam propriedades diferentes, tais como o comportamento externo dos objectos, estrutura interna do objecto, estrutura da hierarquia da herança, ou nas propriedades do software. a) Herança para Implementação Refere-se a situações em que é usada a herança não porque as abstracções (Classificação; Generalização; Agregação e Agrupamento) a serem herdadas são ideais do ponto de vista conceptual, mas porque contem propriedades apropriadas para nova abstracção ainda em construção. Este tipo privilegia a economia de esforço na codificação, o espaço de armazenamento e a velocidade da execução. Os investigadores criticam este modelo. Posteriormente foram adoptados variações na implementação da herança – Cancelamento; - Optimização, e - Conveniência (Rumbaugh citado em Taivalsaari, 1996). A herança simples é um mecanismo existente no paradigma orientado a objectos que permite a reutilização da estrutura e do comportamento de uma classe ao definirem-se novas classes. Quando se define uma nova classe apenas algumas propriedades que diferem das propriedades da classe já existente precisam de ser declaradas explicitamente. Todas as outras propriedades são automaticamente extraídas da classe já existente e incluídas na nova classe (Taivalsaari, 1996). A classe que herda o comportamento é chamada subclasse e a que o definiu chama-se superclasse (A máquina virtual Zero suporta herança simples (Schofield et al., 2004).
5
Conforme referido anteriormente, grande parte dos sistemas orientados a objectos permitem herança múltipla (figura 2, figura 3 e figura 4). Ou seja, é a possibilidade de se definir uma subclasse com mais de uma superclasse. Conceptualmente, a herança múltipla é necessária para modelar o mundo real. No entanto, pode levar a problemas de implementação nomeadamente colisões (collisions). Herança múltipla utiliza-se para combinar abstracções de igual importância. Em vários casos este tipo de situações podem também ser naturalmente expressas usando roles (“papeis”). As colisões ocorrem quando a herança DGA (Grafo Dirigido Acíclico) contem propriedades sobrepostas (over lapping ). Ou seja, quando o algoritmo de pesquisa (lookup) é incapaz de decidir qual dos objectos vai ser executado. Na herança simples a sobreposição (overlapping) é a possibilidade de alterar as características herdadas de um objecto. Ou seja, ocorre quando um descendente redefine algumas das propriedades herdadas para se diferenciar do seu pai. As propriedades recentemente redefinidas sobrepõem naturalmente as herdadas. Chambers et al (1991) apresentaram duas abordagens para resolver colisões: herança ordenada (ordered inheritance) e herança desordenada (unordered inheritance). Para resolver este problema particular o Self, numa edição mais recente, incluiu um especial sender path tiebreak.
Figura 2 –Herança múltipla em linguagens baseadas em classes
Figura 3 –Herança múltipla em linguagens baseadas protótipos por concatenação Fonte : Taivalsaari (1995)
Figura 4 – Herança múltipla em linguagens baseadas em protótipos por delegação Fonte: Chambers et al. (1991)
Nem todas as linguagens de programação orientada a objectos suportam herança múltipla. Herança dinâmica é a possibilidade de mudar o(s) parent(s) dos objectos dinamicamente em tempo de execução (Chambers et al,1991). Schofield (2007) refere que, no caso da herança ser implementada por delegação (estratégia básica de implementação da herança referida no ponto 3.2) abre-se uma nova possibilidade. O atributo que menciona o pai (parent) do objecto pode ser alterado. Desta forma, um objecto pode ser “filho” de vários objectos, dependendo do tempo de execução (ver figura 5). No ponto 3.4.2 é apresentado um exemplo de implementação de herança dinâmica. Este mecanismo de herança traz custos em termos de programação . O que antes era transparente para o programador agora passa a ser declarado de forma explícita. Para além disso, pode diminuir a performance do sistema e resultarem alguns erros. Para os evitar, é necessário coordenar os vários tipos para realizar uma série de tarefas (Chamber et al., 1991, Schofield, 2007).
6
Figura 5- Herança dinâmica Fonte: , Schofield (2007)
No entanto, também apresenta vantagens. A sua principal vantagem é a possibilidade dos métodos se poderem escrever segundo o tipo do objecto. Ajuda a solucionar erros e a tornar a codificação mais simples (Chamber et al.,1991, Schofield, 2007). O ponto 4.3 apresenta um exemplo de utilização de herança dinâmica. b) Herança para Combinação Refere-se a situações em que a herança é usada para combinar abstracções existentes com múltipla herança ou mixin-based inhertance (herança baseada em mixin) também designada por subclasses abstractas (Bracha e Cook, 1990) Herança baseada em mixin, foi utilizada na linguagem de programação baseada em classes CLOS, Mixin é uma subclasse abstracta que pode ser usada para caracterizar o comportamento de uma variedade de parent classes (classes pai). Define frequentemente métodos novos que executam algumas acções e chamam métodos correspondentes do pai (Bracha e Cook, 1990). Esta utilização de herança aumenta consideravelmente a reutilização de definição de classe, mas ao mesmo tempo pode diminuir a clareza conceptual do sistema requerendo que o mesmo mecanismo de classe tenha de ser usado para três finalidades diferentes duma maneira menos intuitiva. c) Herança para Inclusão Muitas linguagens de programação baseadas em classes não fornecem um mecanismo do módulo separado (por exemplo, Smalltalk), e assim às vezes são necessárias classes para simular os módulos ou funções das bibliotecas. d) Outras utilizações da herança Relação de Generalização – A(s) superclasse(s) é(são) uma generalização da(s) subclasse(s). Relação de Especialização – A(s) subclasse(s) é(são) uma especialização da(s) subclasse(s).
7
Como conclusão pode referir-se que a comunidade científica ainda não chegou a nenhuma conclusão sobre o uso apropriado da herança. Embora a especialização seja considerada como a única razão legítima para a usar.
3.3 Maneiras básicas de implementação da herança: Delegação e Concatenação Herança é o relacionamento entre objectos que garante que um descendente tem as mesmas propriedades dos seus pais, podendo ainda serem-lhe adicionadas, alteradas ou eliminadas propriedades, tornando desta forma o descendente diferenciado dos seus pais. Isto implica que o registo que representa o descendente deva ter, pelo menos, os identificadores que os seus pais têm, podendo ainda o descendente introduzir novos identificadores7 distintos8 ou sobrepostos (overlapping). Cook (1989) refere que de uma maneira geral a herança pode ser definida como uma combinação de operações. R= P ⊕ △R Em que nas estruturas dos registos P = […] e R =[…] R - representa um objecto ou uma classe recentemente definida e P representa as propriedades de um objecto ou de uma classe existente, △R - representa as novas propriedades adicionadas para diferenciar R de P, ⊕ - representa uma operação para combinar △R com as propriedades de P.
Taivalsaary (1995) refere que na memória do computador existem duas formas básicas de definir relações ente coisas: References (referências) ou Contiguity (contiguidade). Baseado neste princípio, existem duas estratégias básicas para implementar herança: Delegação e Concatenação. Delegação é a forma de herança em que as relações do descendente são partilhadas com as relação(ões) do(s) pai(s), ou seja, usando referências. A concatenação, por outro lado, consegue o mesmo efeito, copiando as relações dos pais na relação do descendente. Neste caso, a relação dos descendente será contígua (figura 6). Para ilustrar esta diferença Travalsaari (1995) utiliza o seguinte exemplo: O sistema tem implementado o seguinte objecto- Parent, Parent: - [VAR pv1; Var pv2; METHOD pm1] Variáveis: pv1;pv Método: pm1
Suponhamos que pretendemos definir outro objecto, designado Child que herda as propriedades do pai e é-lhe adicionado mais uma variável e mais um método.
7 Identificadores de objectos são usados por objectos para identificar univocamente outros objectos. Adiciona-se um novo identificador atribuindo ao identificador um nome diferente dos nomes dos identificadores já existentes. 8 A relação classe /subclasse é representada por DGA (Grafo Dirigido Acíclico)
8
Relação do descendente por delegação: Child: - [PARENT p: - parent; VAR cv1; METHOD cm1 ] Relação do descendente por concatenação: Child: - [VAR pv1; VAR pv2; METHOD pm1; VAR cv1; METHOD cm1 ] Nota: Os valores reais das variáveis e execução dos métodos foram omitidos.
O resultado é igual para ambos os casos. O objecto Child responde às mensagens que correspondem a identificadores herdados e adiciona a nova variável e o novo método. No entanto, o modo como a mensagem enviada é executado é diferente. Na delegação se o objecto não implementa uma determinada mensagem delega (repassa) a mensagem ao seu pai. Enquanto que, na concatenação, a relação de cada objecto é auto-introduzida e neste caso não é necessária nenhuma resposta. Borning (1986) apresentou uma linguagem baseada em protótipos que não tinha necessariamente de usar delegação. Demonstrou que utilizando a cópia (cloning) e a possibilidade de adicionar dinamicamente novas propriedades aos objectos, na presença de vários mecanismos de envio de mensagem late-binding, é possível capturar a essência da herança. No entanto, esta linguagem apresentou alguns problemas, nomeadamente na capacidade de memória requerida e na maior dificuldade de cópia e modificação dos objectos. Em ambas as formas básicas de herança – Concatenação e Delegação – são necessários late-bound e self-reference. Caso contrário as operações herdadas não poderiam referir as características adicionadas.
Figura 6 – Delegação e Concatenação Fonte: Taivalsaari (1995). Delegation versus or Concatenation or Cloning is Inheritance too
Em sistemas baseados em classes, a delegação e a concatenação processam-se de forma bastante diferente. A designação concatenação não é um termo muito usado na bibliografia. No entanto, foram definidos alguns conceitos que derivaram de Textual concatenation of program bloks (Cook and Plasberg, 1989), tais como: prefixing (Dahl e at citados Taivalsaari, 1996). Nas versões actuais da linguagem Simula e da sua descendente Beta as relações das classes e das subclasses são auto-introduzidas e independentes umas das outras (Madsen et al. citados em Taivalsaari, 1996). Por outro 9
lado, a implementação da herança em smalltalk, linguagem baseada em classes, é idêntica à herança por delegação implementada em Self, linguagem baseada em protótipos. Em ambas as linguagens as mensagens não resolvidas são enviadas para estruturas separadas. Em Smalltalk é usada uma superclasse, e no Self é utilizado um objecto pai. A tabela 1 apresenta as principais diferenças entre herança por delegação e herança por concatenação. Estratégia de herança Delegação Estratégia de combinação Partilha/Referencias de registos Dependente Dependência de relações (life-time sharing9) Assegurada Herança DAG
Concatenação Cópia/Contiguidade Independente (creation-time sharing10) Nivelados
Tabela 1- Delegação versus Concatenação Fonte: Taivalsaari (1996)
Como conclusão podemos referir que tanto a concatenação como a delegação são mecanismo de herança utilizadas tanto em linguagens baseadas em objectos como linguagens baseadas em protótipos (ver tabela 2).
Sistemas baseados em classes
Sistemas baseados em protótipos
Delegação Smalltalk Ómega
Concatenação Simula Beta
Self IO Máquina virtual Zero
Kevo Omega
(ainda não possui uma linguagem de programação de alto nível)
Tabela2 – Esquema de herança em diferentes sistemas Fonte: Taivalsaari (1995); Dekorte S. (?);Schofield (2004,2007); Ungar, , Holzle (1991)
No ponto 4 vão ser abordado 3 sistemas de programação OO, baseados em protótipos (Self, Kevo, e a maquina virtual Zero). 3.3. Vantagens e Desvantagens Delegação versus Concatenação As duas estratégias de implementação de herança apresentam vantagens e desvantagens. A tabela 3. referencia algumas dessas vantagens
9 Life-time sharing refere a partilha física entre pais e filhos 10 Creation-time sharing: a partilha ocorre apenas enquanto o processo receptor está em progresso.
10
Herança
Vantagens
Desvantagens
- O modelo conceptual é extremamente simples e fácil de ensinar (1).
- É menos flexível que o Self em tempo de execução (1) (3).
- Os objectos são auto-suficientes, ou seja para se compreender o ambiente de um objecto não é necessário compreender e hierarquia da estrutura (1).
- Não é possível modificar os pais de um objecto em tempo de execução, (1).
- Podem ser adicionadas novas variáveis que se propagam facilmente aos outros elementos do grupo (1).
Concatenação
- Devido à natureza linear dos objectos não pode ser dada prioridade à herança múltipla, como no Self (1).
- A estrutura linear, do objecto autosuficiente permite optimizar o envio da mensagem (1). - As relações implícitas do objecto escondem a implementação das decisões sobre a partilha/duplicação e são, assim, mais apropriadas para a programação concorrente e distribuída (1). - A combinação da relação do objecto não requer nenhum conceito de partilha (1). - Espaço eficiente de partilha (2). - Utilização conveniente da herança dinâmica. Preserva as mudanças (2)
Delegação
- Privilegia a herança dinâmica (4)
- Cria dependências da Web tornando os sistemas frágeis (2) . - Não adaptável ao ensino (3). - Difícil de implementar (3).
-Integra bem linguagens/ambientes (2) - Maleabilidade e reusabilidade (4). - Facilidade de construção dos programas, manutenção e extensão (4).
Tabela3 – Vantagens e Desvantagens das estratégias de implementação da herança Fonte: (1) Taivalsaari (1995, 1996); (2) Cardelli (3) Schofield (2004,2007); (4) Chambers, et al. (1991)
4. Linguagens de programação OO baseadas em protótipos
4.1 Self O Self é a linguagenm de programação orientada a objectos baseada em protótipos de referência (Taivalsaari, 1996). É similar ao Smalltalk (Smith e Ungar, 1995) na sintaxe e na semântica, mas enquanto o Smalltalk usa o paradigma baseado em classes, o Self usa protótipos.
Mecanismo Objectos Na linguagem Self, os objectos são compreendidos através de slots Um slot tem um nome e uma referência a um objecto. Os slots de um objecto contêm o seu comportamento e o seu estado.
11
A interacção com um objecto é feita através da emissão de uma mensagem. O nome da mensagem, chamado selector, é combinado com o nome do slot. O slot responde de acordo com o seu tipo. Há dois tipos de slots: method slots, e data slots. O method slot contem um método (isto é um código executável) (Ungar e Smith, 1987). Quando uma mensagem é recebida, é devolvido o resultado da execução do método. Os data slots são slots que contêm qualquer tipo do objecto que não é executável, bem como variáveis de classe na programação baseada classe. Quando uma mensagem é recebida por um data slot retorna simplesmente o conteúdo desse objecto. Os objectos novos são criados copiando os já existentes. Em seguida, para se obter o comportamento desejado, adicionam-se slots aos objectos novos (Ungar et al., 1991, Chambers et al. 1991). Herança A herança no Self é efectuada fazendo data slots em parent slots. Os parent slots delegam mensagens nos objectos que são referidos no caso de o selector de uma mensagem não ser combinado com nenhum slot do objecto. Isto significa que um método somente tem que ser escrito uma vez. Quando contido num objecto especial, chamado trait, esse método pode ser usado por qualquer outro objecto (Chambers et al., 1991).
4.2 Kevo Kevo é uma pequena linguagem de programação OO baseada em protótipos desenvolvida por Taivalsaari (1992). É baseada na ideia de que uma linguagem de programação, baseada em protótipos, não tem obrigatoriamente que suportar delegação, como o Self. Conforme referido anteriormente, Kevo usa um mecanismo de herança chamado concatenação (Taivalsaari, 1992, 1995, 1996).
Mecanismo Objectos Em Kevo, os objectos são representados como unidades independentes que contêm todas as propriedades para o efeito desejado. Isto significa que, ao contrário do Self, os objectos não compartilham métodos ou dados, pelo menos não logicamente. Para simplificar a programação, a linguagem Kevo suporta objectos similares que são agrupados juntos. Suporta também modificações do groupwise destes objectos. Isto significa que, se um programador alterar um objecto altera também as propriedades dos outros objectos do grupo (Taivalsaari, 1992, 1995,1996). Herança Conforme mencionado anteriormente, Kevo não suporta delegação. Os objectos herdam as propriedades dos pais, através de mecanismo de concatenação. A concatenação fornece um par de funções de cópia, e diversas funções de modificação. Deste modo, para criar um novo tipo do objecto, o programador precisa de copiar um objecto existente, e aplicar-lhe as operações necessárias para a sua modificação. As operações da concatenação suportam também a modificação do groupwise. 12
Taivalsaari (2005) refere o seguinte exemplo: O objecto GraphicThing foi criado por clonagem do objecto Clonable GraphicThing :- Clonable.clone; GraphicThing ADDS [ VAR x :- 20; VAR y :- 10; METHOD draw :- { ... };
] Os objectos Ball and Bat foram construídos de uma maneira similar clonando o objecto GraphicThing, e depois foram-lhe adicionadas novas variáveis e operações. Ball :- GraphicThing.clone; Ball ADDS [ VAR xStep :- 1; VAR yStep :- 1; METHOD bounce :- { ... increment ball location ... self.draw... }; METHOD serve :- { ... initialize ball location ... self.draw ...};
] Bat :- GraphicThing.clone; Bat ADDS [ METHOD moveUp :- { ... decrement y-coordinate ... self.draw... }; METHOD moveDown :- { ... increment y-coordinate ... self.draw ...};
] Na figura 7 vê-se de uma forma gráfica a codificação referida anteriormente.
Figura 7- Herança do protótipo por concatenação Fonte: Taivalsaari,1992
13
4.3 Máquina Virtual Zero Zero é uma máquina virtual, pequena e simples (Zero, 2007 e Schofield et al., 2004), baseada em protótipos, que teve origem em 2003, na Universidade de Vigo e foi inspirada em “The Design and Evolution of C++”, de Bjarne Stroupstrup, em 1991. Os objectivos da sua criação foram os seguintes: 1º Dotar os estudantes de um sistema OO puro, onde seja possível incluir persistência ortogonal baseada em containers; 2º Proporcionar que as linguagens de programação OO baseadas em protótipos se tornem adequadas à docência; e 3º Ser um sistema minimalista, ou seja pequeno e simples, nomeadamente no que se refere à sua performance. (Zero, 2007) Esta máquina é constituída por um macroassemblador “zm” (permite programação); um assemblador (permite executar operações básicas directamente sobre a máquina, tais como: Criação de objectos, enviar mensagens; incluindo controlo de excepções); Linguagem de programação Prowl (permite programação de alto nível); Máquina virtual (executa os objectos criados com o assemblador); Livraria standard interna (Zero, 2007).
Mecanismo Objectos Conforme foi referido anteriormente esta máquina virtual é baseada em protótipos. Contém dois grandes grupos de registos. O primeiro grupo, é composto por um acumulador (_acc) - que guarda a referência resultante da instrução anterior; Self em Java (_.this) – que guarda o objecto que está a executar o método e (_.exc) o registo que guarda as excepções. O segundo grupo guarda os registos gerais que podem ser utilizados para qualquer propósito (Schofield et al., 2004; Zero, 2007). Não existe uma divisão especial entre objectos e protótipos. Os protótipos são objectos definidos em tempo de compilação. Os objectos criam-se através da clonagem dos primeiros, em tempo de execução. É, ainda, possível, em tempo de compilação clonar um objecto, eliminar-lhe ou adicionar-lhe atributos e métodos e aplicá-lo como protótipo. É possível, também, criar um objecto vazio herdado de outro, passando a mensagem createchild() e adicionar-lhe os métodos e os atributos(variáveis) necessários (Schofield et al., 2004). As mensagens e os métodos executam-se utilizando a mesma sintaxe que a linguagem Java, <nomeObjecto>.<nome.método>. É possível que entre o nome do objecto e o nome do método existam nomes de atributos, que devolvem a referência para um novo objecto. Herança Simples A herança suportada é do tipo simples. Todos os objectos derivam do objecto Object. É o início da hierarquia da herança. Este objecto fornece as operações básicas a todos os outros objectos.
14
Existe um atributo especial denominado parent que identifica o pai de um objecto. No exemplo seguinte PruebaPunto herda de Console Application. ! ================================ ! Creación de un objeto Punto ! Ejemplo de programación en Zero ! ================================ object Punto attribute + x = 0 attribute + y = 0 method + mueve(a, b) isInstanceOf Float, a jumpOnTrueTo testB throw EMismatch :testB isInstanceOf Float, b jumpOnTrueTo fin throw EMismatch
! No lo es ! Es un número ? ! No lo es
:fin x=a y=b return endMethod method + toString() reference toret toret = "(" toret = toret.concat(x.toString()) toret = toret.concat(", ") toret = toret.concat(y.toString()) toret = toret.concat(")") return toret endMethod endObject object PruebaPunto : ConsoleApplication method + doIt() reference x = 100 reference y = 100 reference miPunto = Punto.copy("")
! Copiar el objeto
__this.prepare() ! Inicializaciones miPunto.mueve(x, y) ! mover al punto System.console.write(miPunto) ! Visualizar el punto System.console.lf() return endMethod endObject Fonte: Schofield J. (2007)
Herança Dinâmica Este sistema suporta, também, herança dinâmica. Como foi referido anteriormente, existe um atributo parent que, como qualquer outro, podem ser-lhe alteradas as suas propriedades através do seu próprio objecto. Esta modificação é conhecida como herança dinâmica. Em seguida apresenta-se um exemplo de implementação de herança dinâmica. O objecto muda de pai mediante o cumprimento de uma condição.
15
! Herencia dinámica -- demostración ! Debe ser guardado como "Dinamica.zm" object MapaVacio : Map method + lookUp(name) throw EObjectNotFound return endMethod method + modify(name, obj) throw EObjectNotFound return endMethod method + add(name,obj) System.console.write("MapaVacio::add") System.console.lf() __this.^add(name, obj) parent = MapaNoVacio return endMethod endObject Fonte: Extracto de exercício 4 (Schofield J. ,2007)
O protótipo MapaVacio é utilizado quando o vector está vazio. Passa a ser parent quando se introduzem elementos nele próprio. A possibilidade de troca do parent simplifica bastante a implementação dos métodos modify e add 5. Conclusões A herança é um mecanismo fundamental na construção de programas. Veio trazer vários benefícios à programação, tais como: modelação, simplificação e reutilização. Pode ser vista sobre diferentes aspectos. Usos diferentes focalizam propriedades diferentes, tais como: o comportamento externo dos objectos, estrutura interna do objecto, estrutura da hierarquia da herança ou propriedades das linguagens de programação. Embora seja um assunto amplamente estudado pela comunidade científica (Liberman, 1986; Borning, 1986; Cook, 1989; Bracha e Cook, 1990; Ungar et al., 1987,1991; Taivalsaari, 1992, 1995, 1996; entre outros) ainda não existe um consenso sobre como a mesma deve ser usada. No entanto, a especialização conceptual foi considerada como a única razão legítima para usar a herança. Independentemente do sistema orientado a objectos ser baseado em classes ou em protótipos existem duas formas básicas de implementação da herança: Delegação e Concatenação (Taivalsaari, 1996). Ambas as formas permitem responder a mensagens que correspondem a identificadores herdados que, posteriormente, poderão ser alterados. No entanto, o modo como a mensagem enviada é executado é diferente. Na delegação, se o objecto não implementa uma determinada mensagem, delega (repassa) a mensagem ao seu pai; enquanto que na concatenação a relação de cada objecto é autointroduzida e, neste caso, não é necessária nenhuma resposta (Taivalsaari, 1996, ). Chambers et. al (1991),Stein et al (1988) e (Schofield (2007) referem que, caso a herança seja implementada por delegação, abre uma nova possibilidade, denominada
16
herança dinâmica - possibilidade de mudar dinamicamente os pais dos objectos em tempo de execução. Desta forma, um objecto pode ser “filho” de vários objectos, dependendo do tempo de execução. Existem vários sistemas de programação OO baseados em protótipos que usam diferentes mecanismos, como acabamos de ver no ponto anterior. Contudo o conceito base é o mesmo – herança. Os sistemas baseados em protótipos, que implementam herança por delegação, são mais flexíveis, partilham o espaço de uma forma mais eficiente, utilizam herança dinâmica, preservam a mudança, integram bem linguagens e ambientes, permitem maleabilidade e reusabilidade. Contudo, são sistemas mais frágeis e a implementação é mais difícil (Taivalsaari (1995, 1996); Cardelli (1988); Schofield (2004,2007); Ungar (1991); Stein, et al. (1988); Chambers, et al. (1991). Embora o conceito de herança tenha sido introduzido pela linguagem SIMULA, em 1960, com a designação de concatenation (Dahl et al, 1972 e Nygaard e Dahl, 1978 citados em Taivalsaari, 1996) ainda é um assunto de interesse da comunidade científica.
6. Bibliografia BORNING, A.H.(1986). Classes versus prototypes in object-oriented languages. Em Proceedings of ACM/IEEE Fall Joint Computer Conference, pp.36-40 BRACHA, G. AND COOK, W. 1990. Mixin-based inheritance. Em OOPSLA/ECOOP’90 Conference Proceedings (Ottawa, Canada, Oct. 21–25). ACM SIGPLAN Not. 25, 10 (Oct.), 303–311. CARDELLI L. (1988).A Semantics of Multiple Inheritance, CiteSeer.IST, Information and Computation 76, 138-164 CHAMBERS, C., UNGAR, D., CHANG, B.-W. AND HO¨ LZLE, U. (1991). Parents are shared parts of objects: inheritance and encapsulation in Self. Lisp Symbolic Comput. 4, 3 (Jun.). COOK W. R. (1989). A Denotational Semantics of Inheritance. PhD thesis, Brown University PhD Thesis,. COOK W. R. e PALSBERG J. (1989). A denotational semantics of inheritance and its correctness. Information and Computation, 114(2):329{350. DECORTS Steve (?) prototype-based em languages, on rainer blome´s proto page em http://www.dekorte.com/docs/protos/ LIEBERMAN, H. 1986. Using prototypical objects to implement shared behavior in object-oriented systems. Em OOPSLA’86 Conference Proceedings (Portland, Oregon, Sept. 26–Oct. 2). ACM SIGPLAN Not. 21, 11 (Nov.), 214–223. MORALES P. (2002). Programación Orientada a Objetos: Clases versus Prototipos. Novática, num 144, pg 69-72.
17
SCHOFIELD G., ROSELLÓ E., DACOSTA J., COTA PEREZ M.. (2004). " Programación orientada a objetos educativa en una máquina virtual orientada a objetos ". Em proceedings of the VII Congreso Iberoamericano de Informática Educativa, Monterrey, México SCHOFIELD García Perez- (2007). Tansparencias resumen del curso, y ejercicios, Plataforma Faitic, Universidade de Vigo. Em http://trevinca.ei.uvigo.es/~jgarcia/cdTO/ SCHOFIELD García Perez-, ROSELLÓ E García.,TORRES Martínez, D. PÉREZ COTA, M.(2004). Programación orientada a objetos educativa en una máquina virtual orientada a objetos. Em proceedings of the VII Congreso Iberoamericano de Informática Educativa, Monterrey, México SMITH e UNGAR (1995). “Programming as an experience, the inspiration for Self”. European Congress on ObjectOriented Programming, 1995. STEIN, L. A. (1989). Towards a unified method of sharing in object-oriented programming. Em Workshop on Inheritance Hierarchies in Knowledge Representation and Programming (Viareggio, Italy, Feb. 6–8). STEIN, L. A., LIEBERMAN, H. e UNGAR, D. (1988). A shared view of sharing: the treaty of Orlando. In Object-Oriented Concepts, Applications and Databases, W. Kim and F. Lochowsky, Eds. Addison-Wesley, 31–48. Taivalsaari A.(1992). Kevo - a prototype based object-oriented language based on concatenation and module operations. University of Victoria, Victoria, B.C, Canada. TAIVALSAARI Antero (1995). Delegation versus or Concatenation or Cloning is Inheritance too. OOPS Messenger 6(3): 2049 TAIVALSAARI Antero (1996). On the Notion of Inheritance. In ACM Comput. Surv. 28(3): 438-479 UNGAR, Chambers et al. (1991). “Organizing programs without classes”. Lisp and Symbolic Computation 4(3), Kluwer Academic Publishers, UNGAR, D. e SMITH, R. B. (1987). Self: the power of simplicity. In OOPSLA’87 Conference Proceedings (Orlando, Florida, Oct. 4–8). ACM SIGPLAN Not. 22, 12 (Dec.), 227–241. WEGNER, P. e ZDONIK, S. B. (1988). Inheritance as an incremental modification mechanism or what Like is and isn’t like. Em ECOOP’88: European Conference on Object-Oriented Programming (Oslo, Norway, Aug. 15–17). Lecture Notes in Computer Sci. 276, Springer- Verlag, 55–77. Zero (2003). Em http://trevinca.ei.uvigo.es/~jgarcia/TO/zero/index.html
18