Digamos que eu tenha uma interface FooInterface
que tenha a seguinte assinatura:
interface FooInterface {
public function doSomething(SomethingInterface something);
}
E uma classe concreta ConcreteFoo
que implementa essa interface:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
}
}
Eu gostaria ConcreteFoo::doSomething()
de fazer algo único se ele passou por um tipo especial de SomethingInterface
objeto (digamos que seja chamado SpecialSomething
).
Definitivamente, é uma violação do LSP se eu fortalecer as condições prévias do método ou lançar uma nova exceção, mas ainda assim seria uma violação do LSP se eu fiz um caso especial de SpecialSomething
objetos enquanto fornecia um substituto para SomethingInterface
objetos genéricos ? Algo como:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
if (something instanceof SpecialSomething) {
// Do SpecialSomething magic
}
else {
// Do generic SomethingInterface magic
}
}
}
doSomething()
método seria a conversão de tipo paraSpecialSomething
: se receberSpecialSomething
, retornaria o objeto sem modificação, enquanto que, se receber umSomethingInterface
objeto genérico , executaria um algoritmo para convertê-lo em umSpecialSomething
objeto. Como as pré-condições e pós-condições permanecem as mesmas, não acredito que o contrato tenha sido violado.y = doSomething(x)
entãox.setFoo(3)
e descobrir quey.getFoo()
retorna 3?SpecialSomething
objeto. Embora, por uma questão de pureza, eu também pude ver renunciar à otimização de caso especial quando o objeto passouSpecialSomething
e apenas executá-lo através do algoritmo de conversão maior, uma vez que ainda deve funcionar porque também éSomethingInterface
objeto.Não é uma violação do LSP. No entanto, ainda é uma violação da regra "não faça verificações de tipo". Seria melhor projetar o código de forma que o especial surja naturalmente; talvez
SomethingInterface
precise de outro membro que possa fazer isso, ou talvez você precise injetar uma fábrica abstrata em algum lugar.No entanto, essa não é uma regra rígida e rápida, portanto, você precisa decidir se a troca vale a pena. No momento, você tem um cheiro de código e um possível obstáculo para aprimoramentos futuros. Livrar-se disso pode significar uma arquitetura consideravelmente mais complicada. Sem mais informações, não sei dizer qual é o melhor.
fonte
Não, aproveitar o fato de que um determinado argumento não fornece apenas a interface A, mas também A2, não viola o LSP.
Apenas certifique-se de que o caminho especial não tenha pré-condições mais fortes (além das testadas na decisão de adotá-lo), nem pós-condições mais fracas.
Os modelos C ++ geralmente fazem isso para fornecer melhor desempenho, por exemplo, exigindo
InputIterator
s, mas fornecendo garantias adicionais se chamados comRandomAccessIterator
s.Se você precisar decidir em tempo de execução (por exemplo, usando a conversão dinâmica), tome cuidado com a decisão de qual caminho seguir consumindo todos os seus ganhos potenciais ou até mais.
Tirar proveito do caso especial geralmente é contra o DRY (não se repita), pois você pode precisar duplicar o código e contra o KISS (Keep it Simple), pois é mais complexo.
fonte
Há uma troca entre o princípio de "não faça verificações de tipo" e "segregar suas interfaces". Se muitas classes fornecem um meio viável, mas possivelmente ineficiente, de executar algumas tarefas, e algumas delas também podem oferecer um meio melhor, e há uma necessidade de código que possa aceitar qualquer categoria mais ampla de itens que possam executar a tarefa. (talvez ineficientemente), mas, em seguida, execute a tarefa da maneira mais eficiente possível, será necessário que todos os objetos implementem uma interface que inclua um membro para dizer se o método mais eficiente é suportado e outro para usá-lo, se for, ou então faça com que o código que recebe o objeto verifique se ele suporta uma interface estendida e, se for o caso, faça a conversão.
Pessoalmente, sou a favor da abordagem anterior, embora deseje que estruturas orientadas a objetos como o .NET permitam que interfaces especifiquem métodos padrão (tornando interfaces maiores menos dolorosas de se trabalhar). Se a interface comum incluir métodos opcionais, uma única classe de wrapper poderá manipular objetos com muitas combinações diferentes de habilidades, enquanto promete aos consumidores apenas as habilidades presentes no objeto original. Se muitas funções forem divididas em interfaces diferentes, será necessário um objeto wrapper diferente para cada combinação diferente de interfaces que os objetos quebrados possam precisar suportar.
fonte
O princípio de substituição de Liskov é sobre subtipos que agem de acordo com um contrato de seu supertipo. Portanto, como o Ixrec escreveu, não há informações suficientes para responder se é uma violação do LSP.
O que é violado aqui é um princípio fechado aberto. Se você tem um novo requisito - faça mágica de SpecialSomething - e precisa modificar o código existente, definitivamente está violando o OCP .
fonte