310 votos

¿Cuál es el principio de sustitución Liskov?

He oído que el principio de sustitución de Liskov (LSP) es un principio fundamental del diseño orientado a objetos. ¿Qué es y cuáles son algunos ejemplos de su uso?

284voto

m-sharp Puntos 4349

Un gran ejemplo que ilustra LSP (dado por el Tío Bob en un podcast que he escuchado recientemente) fue cómo, a veces, algo que suena bien en lenguaje natural no acaba de funcionar en el código.

En matemáticas, un Cuadrado es un Rectángulo. De hecho, se trata de una especialización de un rectángulo. "Es un" te hace querer a este modelo con la herencia. Sin embargo, si en el código que hizo que la Plaza se derivan de Rectángulo, luego un Cuadrado debe ser utilizable en cualquier lugar que usted espera de un rectángulo. Esto hace que por algún extraño comportamiento.

Imagine que usted había SetWidth y SetHeight métodos en el Rectángulo de la clase base; esto parece perfectamente lógico. Sin embargo, si el Rectángulo de referencia apuntaba a una Plaza, entonces SetWidth y SetHeight no tiene sentido porque la configuración de uno habría de cambiar al otro para que coincidan. En este caso la Plaza de falla, el de Sustitución de Liskov Prueba con el Rectángulo y la abstracción de tener Plaza de heredar de Rectángulo es un mal.

LiskovSubtitutionPrinciple_52BB5162.jpg

Ustedes deben de consultar a la otra invaluable SÓLIDOS Principios de Pósters de Motivación.

214voto

NotMyself Puntos 7567

El Principio de Sustitución de Liskov (LSP) es un concepto en la Programación Orientada a Objeto que los estados:

Las funciones que utilizan punteros o las referencias a base de las clases deben ser capaz de utilizar objetos de las clases derivadas sin saberlo.

En su corazón LSP es acerca de las interfaces y los contratos, así como la forma en que se decidió cuando para extender una clase contra el uso de otra estrategia, tales como la composición para lograr su objetivo.

La manera más eficaz que he visto para ilustrar este punto estaba en Cabeza de la Primera OOA&D. Presentan un escenario en el que eres un desarrollador en un proyecto para construir un marco para los juegos de estrategia.

Se presenta una clase que representa a una junta que se parece a esto:

Class Diagram

Todos los métodos de toma de coordenadas X e y como parámetros para localizar el azulejo de la posición en la matriz bidimensional de los Azulejos. Esto permitirá que un desarrollador de juegos para administrar unidades en la junta directiva durante el transcurso del juego.

El libro va a cambiar los requisitos para decir que el juego de marco de trabajo también debe apoyar 3d juego de juntas para dar cabida a los juegos de vuelo. Así que un ThreeDBoard clase se introduce, que se extiende de la Junta.

A primera vista, esta parece una buena decisión. La junta ofrece a la Altura y la Anchura de las propiedades y ThreeDBoard proporciona el eje Z.

Donde se rompe es cuando se mira a todos los demás miembros heredados de la Junta. Los métodos para AddUnit, GetTile, GetUnits y así sucesivamente, todos tienen ambos parámetros X e y en la Junta directiva de la clase, pero el ThreeDBoard necesita un parámetro Z así.

Así que usted debe implementar los métodos de nuevo con una Z parámetro. El parámetro Z no tiene contexto a la Junta directiva de la clase y los métodos heredados de la Junta directiva de la clase perder su significado. Una unidad de código de intentar utilizar el ThreeDBoard clase es la clase base de la Junta sería muy fuera de suerte.

Tal vez deberíamos buscar otro enfoque. En lugar de ampliar la Junta, ThreeDBoard debe estar compuesta de la Junta de objetos. Una Junta de objeto por unidad de eje Z.

Esto nos permite usar bien orientada a objetos en principios como el de la encapsulación y la reutilización, y no viola la LSP.

62voto

Konrad Rudolph Puntos 231505

LSP preocupaciones de los invariantes. Su junta de ejemplo se rompe desde el principio, porque las interfaces simplemente no coinciden.

Un mejor ejemplo sería el siguiente (las implementaciones omitido):

class Rectangle {
    int getHeight() const;
    void setHeight(int value);
    int getWidth() const;
    void setWidth(int value);
};

class Square : public Rectangle { };

Ahora tenemos un problema, aunque la interfaz de los partidos. La razón es que hemos violado (implícita) de invariantes. La forma de getters y setters de trabajo, Rectangle deberá cumplir con los siguientes invariantes:

void invariant(Rectangle& r) {
    r.setHeight(200);
    r.setWidth(100);
    assert(r.getHeight() == 200 and r.getWidth() == 100);
}

Sin embargo, esta invariante debe ser violado por una correcta aplicación de la Square, por lo tanto no es un sustituto válido de Rectangle.

39voto

Phillip Wells Puntos 2625

Robert Martin tiene un excelente papel en el principio de sustitución Liskov aquí. Se analizan las formas sutiles y no tan sutiles en el cual puede ser violado el principio.

26voto

Shelby Moore III Puntos 2088

LSP es necesario en algunas código piensa que es llamar a los métodos de un tipo T, y, sin saberlo, puede llamar a los métodos de un tipo Sdonde S extends T (es decir. S hereda, se deriva de, o es un subtipo de la supertipo T).

Por ejemplo, esto ocurre cuando una función con un parámetro de entrada de tipo T, se llama (es decir. se invoca), con un argumento de valor de tipo S. O, cuando un identificador de tipo T, se asigna un valor de tipo S.

val id : T = new S() // id thinks it's a T, but is a S

LSP requiere de las expectativas (es decir. invariantes) para los métodos de tipo T (p. ej. Rectangle), no deben ser violados cuando los métodos de tipo S (p. ej. Square) son llamados en su lugar.

val rect : Rectangle = new Square(5) // thinks it's a Rectangle, but is a Square
val rect2 : Rectangle = rect.setWidth(10) // height is 10, LSP violation

Incluso un tipo con inmutable campos todavía tiene invariantes, por ejemplo. el inmutable Rectángulo incubadoras esperar dimensiones de forma independiente modificado, pero la inmutable Plaza de incubadoras de violar esta expectativa.

class Rectangle( val width : Int, val height : Int )
{
   def setWidth( w : Int ) = new Rectangle(w, height)
   def setHeight( h : Int ) = new Rectangle(width, h)
}

class Square( val side : Int ) extends Rectangle(side, side)
{
   override def setWidth( s : Int ) = new Square(s)
   override def setHeight( s : Int ) = new Square(s)
}

LSP requiere que cada método del subtipo S debe tener contravariante parámetro de entrada(s) y una covariante de salida.

Contravariante significa que la varianza es contrario a la dirección de la herencia, es decir. el tipo Ti, de cada parámetro de entrada de cada método del subtipo S, debe ser el mismo o un supertipo de tipo Si de la correspondiente parámetro de entrada del método correspondiente de la supertipo T.

La covarianza significa que la varianza es en la misma dirección de la herencia, es decir. el tipo So, de la salida de cada método del subtipo S, debe ser el mismo o un subtipo del tipo To de la correspondiente salida de la correspondiente método de la supertipo T.

Esto es debido a que si la persona que piensa que tiene un tipo T, piensa que se trata de una llamada a un método de T, luego de que los suministros de argumento(s) de tipo Si y asigna el resultado al tipo To. Cuando realmente se llama al método correspondiente de S, entonces cada Si argumento de entrada es asignado a un Ti parámetro de entrada, y el So de salida se le asigna para el tipo To.

Además, para cada parámetro de entrada que tiene un tipo de función, la varianza de los roles se invierten, es decir. cada uno de sus parámetros de entrada deben ser covariantes y su salida debe ser contravariante. Esta regla no se aplica de forma recursiva.

Además, para las lenguas (p. ej. Scala) que han definición del sitio anotaciones de variación en parámetros de tipo (es decir. los medicamentos genéricos), la dirección (es decir. co - o en contra-) de la varianza de anotación para cada tipo de parámetro(s) de subtipo S debe ser opuesto o en la misma dirección, respectivamente, para cada parámetro de entrada o de salida (de cada método de S) que contiene el tipo de parámetro. Esto es en relación a la dirección de la varianza de anotación en el correspondiente parámetro de tipo de tipo de parámetro de entrada o de salida.


La tipificación es adecuado donde los invariantes pueden ser enumerados.

Hay mucha investigación en curso sobre cómo el modelo de invariantes, para que sean aplicadas por el compilador.

Typestate (ver página 3) declara y hace cumplir el estado invariantes ortogonal a tipo. Alternativamente, los invariantes puede ser forzada por la conversión de afirmaciones a los tipos. Por ejemplo, afirmar que un archivo es abierto antes de cerrar, entonces File.open() puede devolver un OpenFile tipo, que contiene un método close() que no está disponible en el Archivo. Un tic-tac-toe API puede ser otro ejemplo de empleo de escribir para hacer cumplir invariantes en tiempo de compilación. El tipo de sistema puede ser incluso Turing-completo, por ejemplo. Scala. Dependiente-escribió idiomas y teorema de provers formalizar los modelos de orden superior a escribir.

Debido a la necesidad de la semántica para abstracto sobre la extensión, espero que emplear a escribir para el modelo de invariantes, es decir. unificado de orden superior denotational semántica, es superior a la Typestate. 'Extensión' significa ilimitado, permutada composición de descoordinación, de desarrollo modular. Porque me parece ser la antítesis de la unificación y por lo tanto los grados de libertad, para tener dos mutuamente dependientes de los modelos (p. ej. tipos y Typestate) para expresar la semántica compartida, que no puede ser unificada con cada uno de los otros extensible composición. Por ejemplo, la Expresión del Problema-como extensión fue unificado en la tipificación, la sobrecarga de funciones, y paramétrico escribiendo dominios.

Mi postura teórica es que para el conocimiento de existir (véase la sección "la Centralización es ciego y no aptos"), no será nunca ser un modelo general que se puede aplicar el 100% de cobertura de todos los posibles invariantes en una Turing-completo lenguaje de computadora. Para el conocimiento de existir, inesperadas posibilidades que existen, es decir. el desorden y la entropía siempre debe ser creciente. Esta es la fuerza entrópica. Para probar todos los posibles cálculos de un potencial de extensión, es calcular a priori en toda la extensión posible.

Esta es la razón por la Paralización Teorema existe, es decir. es indecidible si cada posible en un programa de Turing-completo lenguaje de programación que termina. Puede ser demostrado que algunos programas específicos termina (que todas las posibilidades se han definido y calculado). Pero es imposible probar que la posible extensión de ese programa termina, a menos que las posibilidades de extensión de ese programa no es Turing completo (p. ej. a través de dependiente-a escribir). Dado que el requisito fundamental para la Turing-integridad es ilimitado recursividad, es intuitivo para comprender cómo Gödel los teoremas de incompletitud y Russell, la paradoja de aplicar a la extensión.

Una interpretación de estos teoremas incorpora en una generalización de la comprensión conceptual de la fuerza entrópica:

  • Gödel los teoremas de incompletitud: cualquier teoría formal, en el que todas las verdades de la aritmética puede ser demostrado, es incoherente.
  • Russell paradoja: cada regla de pertenencia de un conjunto que puede contener un conjunto, ya sea enumera el tipo específico de cada uno de los miembros o que contiene en sí. Así se fija bien no puede ser extendido o se ilimitada de la recursividad. Por ejemplo, el conjunto de todo lo que no es un vaso de agua, incluye en sí, que incluye en sí, que incluye a sí mismo, etc.... por Lo tanto una regla es inconsistente si (puede contener un conjunto y) no enumera los tipos específicos (es decir. permite todos los tipos no especificados) y no permite sin límites de extensión. Este es el conjunto de los conjuntos que no son miembros de sí mismos. Esta incapacidad para ser a la vez consistente y completo enumerados sobre toda la extensión posible, se Gödel los teoremas de incompletitud.
  • Liskov Substition Principio: generalmente es un problema indecidible si un conjunto es subconjunto de otro, es decir. generalmente, la herencia indecidible.
  • Linsky de Referencia: es indecidible lo que el cómputo de algo que es, cuando se describe o percibida, es decir. la percepción (realidad) no tiene absoluto punto de referencia.
  • El teorema de Coase: no hay ningún punto de referencia externo, por lo tanto, cualquier barrera unbounded externo posibilidades de fallar.
  • Segunda ley de la termodinámica: el universo entero (un sistema cerrado, es decir. todo) tendencias de máximo desorden, es decir. máxima independiente posibilidades.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X