13 de novembro de 2016


De acordo com Liskov (1987), na implementação de uma herança as funções que usam referências para objetos de uma classe devem ser capazes de referenciar objetos das subclasses desta classe sem o saberem. Esse é o LSP, Princípio da Substituição de Liskov. Esse princípio estabelece que as classes filhas devem implementar a superclasse sem alterar o contrato de implementação, ou seja, as classes filhas devem implementar o que a superclasse propõe em seu escopo.

Para mostrarmos um exemplo de violação do LSP, considere uma implementação parecida com o exemplo utilizado por Martin (2006), trata-se da seguinte classe Retangulo:

public class Rectangle {
private double width, height;
public Rectangle(double w, double h) {
width = w; height = h;
}

public double getWidth( ) {return width;}
public double getHeight( ) {return height;}
public void setWidth(double w){width = w;}
public void setHeight(double h){height = h;}
public double area( ) {return width*height;}
}


Agora vejamos a seguinte classe Quadrado, considerando o ponto de vista matemático que todo quadrado é um retângulo:

public class Square extends Rectangle {
    public Square(double w) {
        super(w,w);
    }
    public void setWidth(double w) {

        // Violação do princípio aqui
        super.setWidth(w); super.setHeight(w);
    }
    public void setHeight(double h) {

        // Violação do princípio aqui
        super.setHeight(h); super.setWidth(h);
    }
}

Conforme a indicação dos locais no código acima onde o princípio é violado, essas linhas da implementação desrespeitam o contrato de implementação da superclasse Retangulo na qual quando se modifica a altura, só se modifica a altura e quando se modifica a largura, só se modifica a largura. Na classe Quadrado quando a altura é modificada, a largura também é modificada e vice-versa. Verifique o efeito nas funções de teste abaixo:

static void testLSP(Rectangle r) {
    r.setWidth(4.0);
    r.setHeight(8.0);
    System.out print( “4.0X8.0 = " + r.area( ));
}


public static void main(String [ ] args) {
    Rectangle r = new Rectangle(1.0, 1.0);
    Square s = new Square(1.0);
    testLSP(r); // Funciona
    testLSP(s); // Nao Funciona!
}

Saída:
4.0X8.0 = 32.0 (Certo)
4.0X8.0 = 64.0 (Errado)

Dizemos que testLSP(s) não funciona por que o resultado impresso não corresponde ao esperado pela leitura de seu código.

Vejamos a solução do problema:

public class Rectangle {
    private double width, height;
    public Rectangle(double w, double h) {
        width = w; height = h;
    }

    public double getWidth( ) {return width;}
    public double getHeight( ) {return height;}
    public double area( ) {return width * height;}
    }
   
    public class Square extends Rectangle {
        public Square(double w) { super(w,w); }
}

No exemplo acima, são retirados os métodos setWidth e setHeight das duas classes e após criadas, a altura e a largura não mais podem ser alteradas. Agora a classe Quadrado respeita o contrato de implementação de Retangulo. Novo método testLps:

static void testLSP(Rectangle r) {
    double a = r.getWidth( );
    double b = r.getHeight( );
    System.out print(a+ "X "+b+" = " + r.area( ));
}

Aplicação do teste:

public static void main(String [ ] args) {
    Rectangle r = new Rectangle(4.0, 8.0);
    Square s = new Square(4.0);
    testLSP(r); // Funciona
    testLSP(s); // Funciona!
}

Saída:
4.0X8.0 = 32 (Certo)
4.0X4.0 = 16 (Certo)

Respeitar o contrato significa ter cuidado em respeitar as pré-condições e pós-condições definidas no contrato da superclasse. Não é possível que uma classe filha tenha uma restrição maior que a superclasse sobre as pré-condições, mas, é possível ter uma maior restrição sobre as pós-condições.

Como um exemplo de restrição sobre as pré-condições, podemos citar o exemplo de uma função declarada na superclasse que retorne um número inteiro com valores no intervalo de 1 a 15. A subclasse não poderia restringir esse mesmo atributo com os valores de 4 a 6, mas, poderia ampliar o intervalo, por exemplo, com os valores de 1 a 20. Como um exemplo de restrição sobre as pós-condições podemos citar uma função implementada na superclasse que retorne um valor inteiro no intervalo de 5 a 15. A
subclasse não poderia ampliar o retorno desse método com um intervalo de 5 a 30, mas, poderia restringir, por exemplo, com um intervalo de 5 a 10.

O LSP é outro princípio importante para facilitar a manutenção e expansão do software. Uma vez que uma subclasse cumpra o contrato de implementação da superclasse, ela terá implementado de maneira organizada os métodos necessários e facilitará o reúso e a compreensão do código.

Referência:

LISKOV, B. Data Abstraction and Hierarchy, 1987. Laboratory for Computer Science, Cambridge University, Cambridge, 1987.

0 comentários:

Postar um comentário

Comentários:

Perfil

Formada em Sistemas de Informação e pós-graduada em Engenharia de Software.

Facebook

Views