Sobre o Projeto

  1. Introdução

  2. Leis de Programação Orientada a Objetos

  3. Formalização das Leis

  4. Codificação em Maude

 

Introdução

As Leis de Refinamento para Programação Imperativa estão bem estabelecidas e são bem úteis para guiar o desenvolvimento de software. Embora a Programação Orientada a Objetos esteja sendo usada em grande escala, as Leis de Programação Orientada a Objetos não estão bem estabelecidas, apenas algumas leis estão descritas informalmente na literatura de orientação a objetos.

Leis que justificam a prática de alguns refinamentos de classes, que são normalmente feitos apenas por intuição ou experiência dos programadores que conhecem bem os conceitos de programação orientada a objetos, são extremamente úteis para a evolução de softwares. A restruturação das classes aplicando estas leis trariam uma maior modularidade aos programas e, consequentemente, reuso e extensibilidade.

A formalização destas leis é essencial para determinar quando estas leis devem ser aplicadas, e para indicar precisamente as modificações no código resultantes da aplicação das leis.

 

Leis de Programação Orientada a Objetos

Leis que justificam a prática de alguns refinamentos de classes, que são normalmente feitos apenas por intuição ou experiência de programadores que conhecem bem os conceitos de programação orientada a objetos, são extremamente úteis para a evolução de softwares. A restruturação das classes aplicando estas leis trariam uma maior modularidade e, consequentemente, reuso e extensibilidade. Por exemplo, poderiamos aplicar uma lei de restruturação de classe na definição da classe abaixo,

Class Person{

   private String name;
   private String street;
   private String city;

   public void m(){

      street = exp1;
      city = exp2;
      ...

   }

   public Person(String name,...){
      this.name = name;
      ...
   }

}

e substituí-la por outras duas, tais como



Class ModularPerson{

   private String name;
   private Address address;

   public void m(){

      address.setStreet(exp1);
      address.setCity(exp2);
      ...

   }

   public ModularPerson(String name,...){
      this.name = name;
      address = new Address(...);
      ...
   }

}

que corresponde a Person mas onde os acessos diretos às instâncias das variáveis street e city são substituídas pelas chamadas dos métodos get e set correspondentes usando o atributo address.

class Address{

   private String street;
   private String city;

   public void setStreet(String s){this.street =s;}
   public String getStreet(){return street;}


}

que simplesmente encapsulam os atributos de Person que são associados ao conceito de endereço.

 

Formalização das Leis

 

Para conseguirmos representar e formalizar as leis de refinamento para classes precisamos, primeiramente, formalizar uma representação para as classes, de onde poderemos construir uma classe a partir de suas partes. A idéia da formalização de leis e da representação de uma classes é definir uma algebra para classes. Por exemplo, o operador [ * * *] constrói cabeçalho de classes, tal que

[ M * N * E * IN ]

representa um cabeçalho de uma classe formado por modificadores em M, nome N, superclasse E , e que implementa as interfaces em IN. Já o operador < * * > constrói o corpo de classes de tal forma que a construção

< A * M * I >

representa o corpo de uma classe cujo os atributos são os que estão em A, os métodos os que estão em M e os construtores os que estão em I. A representação de uma classe pode ser dada pela operador :: em conjunto com os dois operadores mostrados anteriormente, por exemplo,

A :: B

define uma classe com o cabeçalho representado por A e corpo por B. O operador @ denota a união, de forma que o corpo de classe representado por <A@B * M * I > teria os atributos em A e B .
Usando estes operadores, a aplicação da lei discutida anteriormente poderia ser precisamente definida pela indicação que a classe

< atribs1 @ atribs2 * mets * inits >

pode ser rescrita como :

< atribs1 @ c * Mcb * Icb<< c = new C(); > ( todos aqueles operadores)

para simplificar a formalização das leis iremos omitir a construção do cabeçalho ou a construção do corpo da classe, se a lei for válida independente desses itens, ou melhor se for válida para qualquer instância de corpo ou cabeçalho de classe.
Nesta lei c é um novo atributo e

· Mcb, assim como Icb, denota os métodos obtidos de M pela substituição dos acessos diretos a variáveis em B por acessos indiretos via c ( usando métodos get e set )
· I << code denota o conjunto de construtores obtido pela inserção de code no início de cada construtor em I.
· O nome(c) e o tipo(c) são respectivamente c e C, e pode ser definido da seguinte forma : [ public * C * null * null ] :: < B * set(B) @ get(B) * null > que é basicamente uma classe formada por atributos em B, métodos get e set para cada atributo em B, e sem construtor, ou melhor com o construtor padrão.

Existem outras leis interessantes que podem ser formalizadas da mesma forma . Por exemplo, considerando que m e m' têm a mesma assinatura. Temos que

<A * M @ m * I > [[ < A * M @ m' * I >

desde que m [[ m'. Esta lei indica basicamente que o refinamento de um método implica no refinamento da classe.

 

Codificação em Maude

Maude é uma linguagem lógica baseada em Lógica de Rescrita, que é uma lógica que tem boas propriedades para dar semântica a linguagens e suporta muito bem computação orientada a objetos. Esta lógica tem uma boa estrutura semântica e lógica.
Por este motivo ela foi escolhida como linguagem de implementação das regras de refinamento. Maude contém como sorts, que pode ser visto como sendo as classes em Java; Operações, nada mais são do que funções sobre os sorts, e podem ser comparadas com os métodos em Java; Módulos, que são usados para agregar os sorts, as operações e outros componentes que fazem parte da linguagem, e podem ser comparados como pacotes em Java .
Antes de iniciarmos a codificação das leis de refinamento em maude, precisamos definir todos os construtores da Linguagem Java em Maude. Esta construção foi baseada na gramática livre de contexto de Java, no entanto foi visto que o poder de expressão de Maude era menor do que a das gramáticas livre de contexto, e por isso em alguns pontos tivemos que enfraquecer a definição da linguagem Java em Maude.
Afim de modularizar a definição da linguagem e desta forma obter uma maior flexibilidade e extensibilidade para nossa definição, definimos cada construtor de Java em módulo separado. Por exemplo, definimos um módulo METHOD, que define um método em java, assim como suas subpartes(cabeçalho e corpo); um módulo FIELD, que representa o conceito de atributo de classe; e CONSTRUCTOR, que é a definição de Construtor em Java. O módulo CLASS é definido a partir da definição destes módulos já existentes.



O desenho acima é apenas um resumo da estrutura que realmente foi implementada, na verdade cada componente do lado direito da seta foi decomposto em subcomponentes, que também foram decompostos até chegar em componentes básicos ou atômicos. A figura abaixo retrata melhor a estrutura da implementação atual, por exemplo o módulo CLASS foi decomposto em CLASSBODY, que trata a parte do corpo de uma classe, como Métodos, Construtores e Atributos. E a outra parte CLASSHEADER que tem a ver com o cabeçalho de uma classe, e tem como elementos o nome da classe, os modificadores( public e abstract, por exemplo), interfaces que ela implementa e superclasse.


O agrupamento em pacotes possibilita uma visão mais clara e mais precisa das entidades, suas dependências, e seus relacionamentos, tornando a implementação mais fácil de ser implementada e mais fácil de ser entendida. Durante a definição encontramos dois tipos de dependências entre os módulos. A dependência OU, que poderia ser comparado com um relacionamento de subtipo, por exemplo o módulo Type pode ser um BasicType, onde estão definidos os tipos como : int, float, char etc, ou um ReferenceType, onde está o tipo array e o tipo classe. Também existe a dependência E, que seria melhor comparada com uma composição. Um exemplo de E pode ser visto no grafo acima, onde uma class é dividida em duas partes um classbody e classheader.

 


Um problema encontrado durante a codificação foi a dependência mútua, que aparece freqüentemente, devido ao fato da gramática de Java ser muito recursiva. Um exemplo claro de uma dependência deste tipo aparece em type. Como já foi dito, para definir type precisamos da definição de basictype e referencetype, que por sua vez precisa da definição de classtype e de arraytype, e esta última de type.





Este conjunto de módulos não podem ser definidos em Maude, por isso é preciso evitar este tipo de construção, mesmo que isto venha a enfraquecer a definição da linguagem