Consistência de Estados e Interfaces
Autor: Kênia | Publicado: 2017-08-09T13:12:00-03:00





Assunto abordado por Cargill (1992), manter a consistência de estados e interfaces é muito importante. Toda operação deve deixar o objeto sobre o qual ela atua em um estado consistente. Para isso, devemos definir os invariantes dos objetos da classe e observar para que todas as construtoras e operações respeitem as condições estabelecidas.

A falta de consistência pode provocar resultados inesperados durante a execução. Imagine o exemplo de implementação de uma pilha com algumas construtoras. Caso o topo seja apontado para um local em uma das construtoras e para outro local em outra construtora, haverá uma inconsistência aí e o algoritmo poderá funcionar de forma inesperada em alguns momentos.


Veja o exemplo de implementação da classe MyString com problemas de consistência:

class MyString {
 
 //Campos privados de MyString
private char[ ] s;
private int length;

//Funções auxiliares privadas de MyString
private void copy(char[]d, char[] a){
int i;
for(i=0; i&lt;a.length &amp;&amp; a[i]!=‘\0’; i++){
d[i] = a[i];
}
if (i &lt; d.length)
d[i] =‘\0’;
}

private void join(char[]d,char[]a,char[]b){
int i,j;
for (i=0; i&lt;a.length &amp;&amp; a[i]!=‘\0’; i++){
d[i]= a[i];
}
for (j=0; j&lt;b.length; j++)
d[i+j] = b[j];
d[i+j] = ‘\0’;
}

private int strlen(char[] a){
int i = 0;
while ((i &lt; a.length) &amp;&amp; (a[i]!=‘\0’)) i++;
return i;
}

//Construtoras de MyString
public MyString() {
s = new char[80]; length = 0;
}
public MyString(int n) {
s = new char[n]; length = n;
}
public MyString(char[ ] p) {
length = strlen(p); s = new char[length + 1];
copy(s,p);
}

public MyString(MyString str){
length = strlen(str); this.s = new char[length];
copy(this.s, str.s);
}

//Operações de String
public void assign(char[ ] f) {
copy(this.s, f);
length = strlen(f);
}
public void print() {
int k = strlen(this.s);
for(int i=0; i&lt; k; i++)
System.out.print(s[i]);
System.out.println();
}

//Definição de String
public void concat(MYString a, MyString b){
length = strlen(a) + strlen(b);
this.s = new char[length];
join(this.s, a.s, b.s);
}
public int length( ) {
return length;
}

}

Observe que existe um problema com consistência de estados nas duas primeiras construtoras do exemplo dado:

public MyString() {
s = new char[80]; length = 0;
}
public MyString(int n) {
s = new char[n]; length = n;
}

O campo length recebe 0 na primeira implementação e n na segunda. Na primeira length é o tamanho da cadeia de caractere dentro da área alocada, enquanto na segunda length é o tamanho da área alocada. Essa implementação está incorreta, pois, ocorreu uma inconsistência na definição no estado do objeto e isso pode causar conflitos durante a execução do código. O correto seria que o campo length estivesse com um tamanho definido de forma igual nas duas construtoras.

Uma solução do problema seria unificar essas duas construtoras para que mantenham a consistência no estado do campo length:

public MyString(int n) throws Erro{
if (n &lt; 0) throw new Erro() ;
s = new char[n]; length = 0;
}

public MyString( ) throws Erro {this(80);}

Mesmo com a solução apresentada para esse problema, estamos com um problema na definição da semântica do campo length, pois, não sabemos ao certo o que ele representa. Verifique nas partes do código original examinadas separadamente a seguir:

// Aqui, length é o tamanho do string armazenado:
public MyString(char[ ] p) {
length = strlen(p); s = new char[len + 1];
copy(s, p);
}

// Aqui, length é o tamanho da área reservada (array):
public MyString(int n) throws Erro{
if (n &lt; 1) new Erro();
s = new char[n]; length = n;
}

// Aqui, length é o tamanho do array ou do string:
public MyString(MyString str){
length = str.length;
s = new char[length + 1];
copy(s, str.s);
}

// Aqui, length é o tamanho do string armazenado:
public void assign(char[ ] c) {
copy(s, c);
length = strlen(c);
}

// Aqui, length é o tamanho do string acrescido de uma unidade
public void concat(String a, String b){
length = a.length + b.length;
s = new char[length+1];
join(s, a.s, b.s);
}

Precisamos definir uma única semântica para o length em todas as operações, então foi registrado na definição da classe o invariante que estabelece length como o tamanho do String. Verifique a solução do problema, na nova versão de MyString:

class MyString {

//Campos Privados de MyString
private char[] s;

/*Com a definição do campo length seguida de seu comentário a seguir, ele é registrado como invariante na definição da classe*/
private int length; // length == strlen(s)

//Funções auxiliaries privadas de MyString
private void copy(char[]d,char[]a) throws Erro{
int i;
if (d==null || a==null) throw new Erro();
for(i=0; i&lt;a.length &amp;&amp; a[i]!=‘\0’; i++){
d[i] = a[i];
}
if (i &lt; d.length) d[i] =‘\0’;
}

private void join(char[]d,char[]a,char[]b) throws Erro{
int i,j;
if (d==null) throw new Erro();
if (a==null || b == null) throw new Erro();
int nc = strlen(a) + strlen(b) + 1;
if (d.length &lt; nc) throw new Erro();
for (i=0; i&lt;strlen(a); i++) d[i] = a[i];
}
for (j=0; j&lt;strlen(b); j++) d[i+j] = b[j];
d[i+j] = ‘\0’;
}

private int strlen(char[] a){
int i = 0;
if ( a == null) return 0;
while ((i &lt; a.length) &amp;&amp; (a[i]!=‘\0’)) i++;
return i;
}

//Construtoras de MyString
public MyString(int n) throws Erro{
if (n &lt; 0) throw new Erro();
s = new char[n];
length = 0;
}
public MyString( ){ this(80);}

public MyString(char[] p) throws Erro {
length = strlen(p);
s = new char[length + 1];
copy(s, p);
}

public MyString(Mystring str){
length = str.length;
s = new char[length+1];
try {copy(s, str.s);} catch Erro { };
}

//Operações de string
public void assign(char[ ] c) throws Erro {
copy(s,c); length = strlen(c);
}

public void print() {
int k = strlen(s);
for (int i=0; i&lt; k; i++)
System.out.print(s[i]);
System.out.println();
}

//Definição de String
public void concat(MyString a, MyString b){
length = a.length + b.length;
s = new char[length+1];
join(s, a.s, b.s);
}

public int length(){
return length;
}
}

Resumindo em quatro passos o que é preciso fazer para manter a consistência de estados e interfaces. Primeiro é necessário identificar quais devem ser os invariantes de uma classe, ou seja, quais condições devem ser respeitadas durante a representação ou alteração de estado de um objeto. Segundo, os estados dos objetos devem ser definidos de forma consistente. Terceiro, a semântica de um campo deve ser a mesma em todas as operações nas quais ele esteja envolvido. Quarto, o invariante deve ser registrado na definição da classe para facilitar a manutenção do código.

Referência:

CARGILL, T. C++ Programming Style, Reading. 1. ed. Reading, MA: Addison-Welsey, 1992.