Digamos que tengo una interfaz FooInterface
que tiene la siguiente firma:
interface FooInterface {
public function doSomething(SomethingInterface something);
}
Y una clase concreta ConcreteFoo
que implementa esa interfaz:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
}
}
Me gustaría ConcreteFoo::doSomething()
hacer algo único si se pasa un tipo especial de SomethingInterface
objeto (digamos que se llama SpecialSomething
).
Definitivamente es una violación de LSP si fortalezco las condiciones previas del método o lanzo una nueva excepción, pero ¿seguiría siendo una violación de LSP si coloco SpecialSomething
objetos especiales al tiempo que proporciono una reserva 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 sería la conversión de tipo aSpecialSomething
: si lo recibeSpecialSomething
, simplemente devolverá el objeto sin modificar, mientras que si recibe unSomethingInterface
objeto genérico , ejecutará un algoritmo para convertirlo en unSpecialSomething
objeto. Como las condiciones previas y posteriores siguen siendo las mismas, no creo que se haya violado el contrato.y = doSomething(x)
, a continuaciónx.setFoo(3)
, y luego encontrar que losy.getFoo()
retornos 3?SpecialSomething
objeto. Aunque en aras de la pureza, también pude ver renunciar a la optimización de casos especiales cuando el objeto pasado esSpecialSomething
y simplemente ejecutarlo a través del algoritmo de conversión más grande, ya que aún debería funcionar debido a que también es unSomethingInterface
objeto.No es una violación del LSP. Sin embargo, sigue siendo una violación de la regla "no hacer verificaciones de tipo". Sería mejor diseñar el código de tal manera que el especial surja naturalmente; tal vez
SomethingInterface
necesite otro miembro que pueda lograr esto, o tal vez necesite inyectar una fábrica abstracta en alguna parte.Sin embargo, esta no es una regla difícil y rápida, por lo que debe decidir si la compensación vale la pena. En este momento tiene un olor a código y un posible obstáculo para futuras mejoras. Deshacerse de él podría significar una arquitectura considerablemente más complicada. Sin más información no puedo decir cuál es mejor.
fuente
No, aprovechar el hecho de que un argumento dado no solo proporciona la interfaz A, sino también A2, no viola el LSP.
Solo asegúrese de que el camino especial no tenga condiciones previas más fuertes (aparte de las probadas al decidir tomarlo), ni condiciones posteriores más débiles.
Las plantillas de C ++ a menudo lo hacen para proporcionar un mejor rendimiento, por ejemplo, requiriendo
InputIterator
s, pero dando garantías adicionales si se llaman conRandomAccessIterator
s.Si tiene que decidir en tiempo de ejecución en su lugar (por ejemplo, utilizando el casting dinámico), tenga cuidado con la decisión de qué camino tomar consumiendo todas sus ganancias potenciales o incluso más.
Aprovechar el caso especial a menudo va en contra de DRY (no se repita), ya que podría tener que duplicar el código, y en contra de KISS (Keep it Simple), ya que es más complejo.
fuente
Existe una compensación entre el principio de "no hacer verificaciones de tipo" y "segregar sus interfaces". Si muchas clases proporcionan un medio viable pero posiblemente ineficiente para realizar algunas tareas, y algunas de ellas también pueden ofrecer un medio mejor, y existe la necesidad de un código que pueda aceptar cualquiera de la categoría más amplia de elementos que pueden realizar la tarea (tal vez de manera ineficiente), pero luego realice la tarea de la manera más eficiente posible, será necesario que todos los objetos implementen una interfaz que incluya un miembro para decir si el método más eficiente es compatible y otro para usarlo si es así, o de lo contrario haga que el código que recibe el objeto compruebe si admite una interfaz extendida y, si es así, lo emite.
Personalmente, estoy a favor del enfoque anterior, aunque deseo que los marcos orientados a objetos como .NET permitan que las interfaces especifiquen métodos predeterminados (haciendo que las interfaces más grandes sean menos dolorosas para trabajar). Si la interfaz común incluye métodos opcionales, entonces una sola clase de contenedor puede manejar objetos con muchas combinaciones diferentes de habilidades, al tiempo que promete a los consumidores solo aquellas habilidades presentes en el objeto envuelto original. Si muchas funciones se dividen en diferentes interfaces, entonces se necesitará un objeto contenedor diferente para cada combinación diferente de interfaces que los objetos envueltos puedan necesitar soportar.
fuente
El principio de sustitución de Liskov se refiere a los subtipos que actúan de acuerdo con un contrato de su supertipo. Entonces, como escribió Ixrec, no hay suficiente información para responder si se trata de una violación de LSP.
Sin embargo, lo que se viola aquí es un principio abierto cerrado. Si tiene un nuevo requisito, hacer algo especial, y tiene que modificar el código existente, definitivamente está violando OCP .
fuente