Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Ola pessoal do imaster tudo bem?
Estou estudando os 5 princípios de SOLID, estou na parte do princípio de substituição de Liskov.
E confesso que estou com muita dificuldade de entender o objetivo deste princípio, já fiz varias buscas no google sobre este assunto, e todos aborda com exemplos confusos.
Poderiam me ajudar a compreender este princípio de substituição de Liskov, numa forma fácil de entender?
Desde já agradeço
Observe então que um objeto [inline]Rectangle[/inline] NÃO É SUBSTITUÍVEL por um objeto [inline]Square[/inline], logo, não deveríamos utilizar herança neste caso.
Não é substituível por que, o quadrado tem a largura e altura sempre que sincronizados, diferente do retângulo que a sua altura e largura são totalmente diferentes. Certo?
Como seria a solução deste problema de quadrado e retângulo?
Não é substituível por que, o quadrado tem a largura e altura sempre que sincronizados, diferente do retângulo que a sua altura e largura são totalmente diferentes. Certo?
É, pode-se colocar dessa forma.
Como seria a solução deste problema de quadrado e retângulo?
NÃO usar herança no caso:
class Sqare {}
class Rectangle {}
Outra maneira simples de entender este princípio.
Jeito errado que quebra o princípio de substituição de Liskov:
abstract class Bird {
function public fly() {}
function public eat() {}
}
// Pássaro corvo
class Crow extends Bird {}
// Pássaro avestruz, porem é um tipo de pássaro que não voa
class Ostrich extends Bird {
function public fly () {
// Avestruz não voa, por este motivo dará um erro quando alguém chamar este método
throw new UnsupportedOpperationException('avestruz não voa');
}
}Jeito certo de se aplicar o princípio de substituição de Liskov:
abstract class Bird {
// Método comer seria o método certo para todos os tipo de pássaro, por que isso que é genérico para todos os tipo de passaro no nosso exemplo
public function eat () {} // comer
}
// Pássaros que voam, implementaria o método fly()
class FlightBird extends Bird {
public function fly () {}
}
// Pássaros que não voa, não precisam do método fly() , assim evitando erros ou bugs
class NonFlightBird extends Bird {}
Como podemos ver foi retirado o método fly() da classe "Bird" , e só ficou o método eat() nela, pois isso que é genérico para todos os pássaros.
E criamos 2 classes que estende "Bird" as classes FlightBird(pássaro que voa) e NonFlightBird(pássaro que não voa), agora classes como avestruz, galinha, pinguim estenderiam a classe NonFlightBird pois eles não precisam do método fly() , assim evitando erros ou bugs. Esse seria o jeito certo de se fazer e não quebrando o princípio de substituição de Liskov.
Espero ter ajudado
Em outras palavras, antes de estender uma classe X, criando uma classe Y, pergunte-se a si mesmo se é possível substituir objetos X por objetos Y sem alterar a funcionalidade do programa.
@Henrique esqueci de falar deste trecho, nessa parte você fala que uma "super classe" que no caso é X, criando uma classe "sub classe" que no caso é Y, as "sub classes" não podem modificar o comportamento da suas "super classe". Certo?
Subclasses podem modificar o comportamento da superclasse, desde que isso não gere efeitos colaterais.
Hum .. entendi agora Henrique :)
Vlw pela ajuda e paciência de vocês
O Princípio de Substituição de Liskov delimita regras para se utilizar a herança, uma vez que a regrinha do é um é meio subjetiva demais.
Em seu lugar, Liskov propões que para se utilizar herança, façamos um outro questionamento: é substituível por?
Em outras palavras, antes de estender uma classe X, criando uma classe Y, pergunte-se a si mesmo se é possível substituir objetos X por objetos Y sem alterar a funcionalidade do programa.
Um exemplo claro onde o é um falha é no caso do Retângulo/Quadrado. Matematicamente, todo quadrado é um retângulo, então, inicialmente, faria sentido o seguinte código:
class Rectangle {}
class Square extends Rectangle {}
Agora vamos pensar em implementação. Um retângulo é um paralelogramo que possui todos os ângulos retos e cujos lados tem medidas iguais 2 a 2:
class Rectangle {
E agora, vamos à classe que representará um quadrado. Como sabemos, para ter um quadrado, precisamos de um retângulo e precisamos garantir ele que possui todos os lados iguais. Fazendo com herança, temos:
class Square extends Rectangle {
Ok, a princípio funciona, mas há um bug: o que acontece se depois de instanciar um objeto [inline]Square[/inline] eu vou lá e resolvo setar um dos lados independentemente?
Aaah, mas podemos forçar algo do tipo:
class Square extends Rectangle {
Legal, agora funciona como esperado, mas você tem 2 atributos que precisa manter sincronizados entre si, além do desperdício de espaço, porque você precisa de um dado só.
Além do mais, quando você altera a largura de um retângulo, você não espera que a altura seja afetada.
Imagine que você utilize essas formas geométricas para algum outro fim, e você tem:
Entretanto, nada te impedirá de chamar essa função como:$obj->doSomethingWithRectangle(new Square(5));
Quando o código executar, sem saber, ele irá alterar não só a largura do objeto, mas também a altura. Isso é conhecido como efeito colateral, mas também pode ser chamado de bomba de bugs.
Observe então que um objeto [inline]Rectangle[/inline] NÃO É SUBSTITUÍVEL por um objeto [inline]Square[/inline], logo, não deveríamos utilizar herança neste caso.