Sobre o Projeto
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.
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.
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