Princípio da Substituição de Liskov
Autor: Kênia | Publicado: 2016-11-13T19:07:00-02:00





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 {&nbsp;&nbsp;&nbsp; public Square(double w) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super(w,w);&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; public void setWidth(double w) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Violação do princípio aqui&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super.setWidth(w); super.setHeight(w); &nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; public void setHeight(double h) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Violação do princípio aqui&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super.setHeight(h); super.setWidth(h); &nbsp;&nbsp;&nbsp; }}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) {&nbsp;&nbsp;&nbsp; r.setWidth(4.0);&nbsp;&nbsp;&nbsp; r.setHeight(8.0);&nbsp;&nbsp;&nbsp; System.out print( “4.0X8.0 = " + r.area( ));}public static void main(String [ ] args) {&nbsp;&nbsp;&nbsp; Rectangle r = new Rectangle(1.0, 1.0);&nbsp;&nbsp;&nbsp; Square s = new Square(1.0);&nbsp;&nbsp;&nbsp; testLSP(r); // Funciona&nbsp;&nbsp;&nbsp; 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 {&nbsp;&nbsp;&nbsp; private double width, height;&nbsp;&nbsp;&nbsp; public Rectangle(double w, double h) {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; width = w; height = h;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; public double getWidth( ) {return width;}&nbsp;&nbsp;&nbsp; public double getHeight( ) {return height;}&nbsp;&nbsp;&nbsp; public double area( ) {return width * height;}&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public class Square extends Rectangle {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 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) {&nbsp;&nbsp;&nbsp; double a = r.getWidth( );&nbsp;&nbsp;&nbsp; double b = r.getHeight( );&nbsp;&nbsp;&nbsp; System.out print(a+ "X "+b+" = " + r.area( ));}Aplicação do teste:public static void main(String [ ] args) {&nbsp;&nbsp;&nbsp; Rectangle r = new Rectangle(4.0, 8.0);&nbsp;&nbsp;&nbsp; Square s = new Square(4.0);&nbsp;&nbsp;&nbsp; testLSP(r); // Funciona&nbsp;&nbsp;&nbsp; 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.