2010-12-13 11 views
70

¿Puede explicar el Principio de sustitución de Liskov (La 'L' de SOLID) con un buen ejemplo de C# que cubre todos los aspectos del principio de una manera simplificada? Si es realmente posible.¿Puedes explicar el Principio de Sustitución de Liskov con un buen ejemplo de C#?

+9

Aquí hay una manera simplificada de pensar sobre esto en pocas palabras: si sigo LSP, puedo reemplazar cualquier objeto en mi código con un objeto Mock, y la nada en el código de llamada tendría que ser ajustada o cambiada para dar cuenta de la sustitución LSP es un soporte fundamental para el patrón Test by Mock. – kmote

+0

Hay más ejemplos de conformidad y violaciones en [esta respuesta] (http://stackoverflow.com/q/20861107/314291) – StuartLC

+0

Véase, por ejemplo, la interfaz 'IList' ... vaya, no importa: D – Boiethios

Respuesta

107

(Esta respuesta ha sido reescrito 05/13/2013, leer la discusión en el fondo de los comentarios)

LSP se trata de seguir el contrato de la clase base.

Por ejemplo, no puede lanzar nuevas excepciones en las subclases, ya que el que usa la clase base no lo esperaría. Lo mismo ocurre si la clase base arroja ArgumentNullException si falta un argumento y la subclase permite que el argumento sea nulo, también una violación de LSP.

Aquí es un ejemplo de una estructura de clases que viole LSP:

public interface IDuck 
{ 
    void Swim(); 
    // contract says that IsSwimming should be true if Swim has been called. 
    bool IsSwimming { get; } 
} 
public class OrganicDuck : IDuck 
{ 
    public void Swim() 
    { 
     //do something to swim 
    } 

    bool IsSwimming { get { /* return if the duck is swimming */ } } 
} 
public class ElectricDuck : IDuck 
{ 
    bool _isSwimming; 

    public void Swim() 
    { 
     if (!IsTurnedOn) 
     return; 

     _isSwimming = true; 
     //swim logic 

    } 

    bool IsSwimming { get { return _isSwimming; } } 
} 

Y el código de llamada

void MakeDuckSwim(IDuck duck) 
{ 
    duck.Swim(); 
} 

Como se puede ver, hay dos ejemplos de patos. Un pato orgánico y un pato eléctrico. El pato eléctrico solo puede nadar si está encendido. Esto rompe el principio LSP ya que debe activarse para poder nadar, ya que IsSwimming (que también forma parte del contrato) no se establecerá como en la clase base.

Por supuesto, puede resolverlo por hacer algo como esto

void MakeDuckSwim(IDuck duck) 
{ 
    if (duck is ElectricDuck) 
     ((ElectricDuck)duck).TurnOn(); 
    duck.Swim(); 
} 

Pero eso sería romper abierto/cerrado principio y tiene que ser implementado en todas partes (y thefore todavía genera código inestable).

La solución adecuada sería la de activar automáticamente el pato en el método Swim y de esta manera hacer que el pato eléctrica se comportan exactamente como lo define la interfaz de IDuck

actualización

Alguien añadió un comentario y lo eliminó. Tenía un punto válido que me gustaría abordar:

La solución para activar el pato dentro del método Swim puede tener efectos secundarios cuando se trabaja con la implementación real (ElectricDuck). Pero eso se puede resolver usando un explicit interface implementation. en mi humilde opinión, es más probable que se tienen problemas por el NO de encenderlo en Swim ya que se espera que va a nadar cuando se utiliza la interfaz IDuck

Actualización 2

reformularon algunas partes para que sea más clara.

+1

@ jgauffin: El ejemplo es simple y claro.Pero la solución que propone, primero: rompe el Principio Abierto Cerrado y no se ajusta a la definición del Tío Bob (ver la parte de conclusión de su artículo) que escribe: "El Principio de Sustitución de Liskov (Diseño AKA por Contrato) es una característica importante de todos los programas que se ajustan al principio de Open-Closed ". ver: http: //www.objectmentor.com/resources/articles/lsp.pdf – pencilCake

+0

No veo cómo se rompe la solución Abierto/Cerrado. Lea mi respuesta nuevamente si se refiere a la parte 'if duck is ElectricDuck'. Tuve un seminario sobre SOLID el jueves pasado :) – jgauffin

+2

@jgauffin: Por favor corrígeme si estoy pensando mal; pero cuando haces tal comprobación de tipo y si hay alguna característica nueva que se agrega a nuestro pato eléctrico y que debería completarse antes de la llamada a TurnOn() (como InflateTheDuck()) significa que tienes que modificar MakeDuckSwim (IDuck duck) método también. Así que no solo extiende su pato eléctrico sino que también modifica el método MakeDuckSwim. Para mí, parece una posible ruptura del Principio Abierto/Cerrado. – pencilCake

6

LSP un enfoque práctico

Dondequiera que buscar ejemplos de LSP C#, la gente ha utilizado las clases e interfaces imaginarias. Aquí está la implementación práctica de LSP que implementé en uno de nuestros sistemas.

Escenario: Supongamos que tenemos 3 bases de datos (Clientes hipotecarios, Clientes de cuentas corrientes y Clientes de cuentas de ahorro) que proporcionan datos de clientes y necesitamos los datos del cliente para el apellido del cliente. Ahora podemos obtener más de 1 detalle de cliente de esas 3 bases de datos contra el apellido dado.

Implementación:

MODELO DE NEGOCIO CAPA: ACCESO

public class Customer 
{ 
    // customer detail properties... 
} 

datos de la capa:

public interface IDataAccess 
{ 
    Customer GetDetails(string lastName); 
} 

Por encima de interfaz se implementa mediante la clase abstracta

public abstract class BaseDataAccess : IDataAccess 
{ 
    /// <summary> Enterprise library data block Database object. </summary> 
    public Database Database; 


    public Customer GetDetails(string lastName) 
    { 
     // use the database object to call the stored procedure to retrieve the customer details 
    } 
} 

Esta clase abstracta tiene un método común "GetDetails" para todas las 3 bases de datos que se extiende por cada uno de las clases de bases de datos como se muestra abajo

HIPOTECA consultar datos del cliente:

public class MortgageCustomerDataAccess : BaseDataAccess 
{ 
    public MortgageCustomerDataAccess(IDatabaseFactory factory) 
    { 
     this.Database = factory.GetMortgageCustomerDatabase(); 
    } 
} 

CUENTA CORRIENTE DE DATOS DE CLIENTE ACCESO:

public class CurrentAccountCustomerDataAccess : BaseDataAccess 
{ 
    public CurrentAccountCustomerDataAccess(IDatabaseFactory factory) 
    { 
     this.Database = factory.GetCurrentAccountCustomerDatabase(); 
    } 
} 

AHORROS DE ACCESO DE DATOS DE CUENTA DE CLIENTE:

public class SavingsAccountCustomerDataAccess : BaseDataAccess 
{ 
    public SavingsAccountCustomerDataAccess(IDatabaseFactory factory) 
    { 
     this.Database = factory.GetSavingsAccountCustomerDatabase(); 
    } 
} 

Una vez que se establecen estas 3 clases de acceso a datos, ahora llamamos nuestra atención sobre el cliente. En la capa Business tenemos la clase CustomerServiceManager que devuelve los detalles del cliente a sus clientes.

capa de negocio:

public class CustomerServiceManager : ICustomerServiceManager, BaseServiceManager 
{ 
    public IEnumerable<Customer> GetCustomerDetails(string lastName) 
    { 
     IEnumerable<IDataAccess> dataAccess = new List<IDataAccess>() 
     { 
      new MortgageCustomerDataAccess(new DatabaseFactory()), 
      new CurrentAccountCustomerDataAccess(new DatabaseFactory()), 
      new SavingsAccountCustomerDataAccess(new DatabaseFactory()) 
     }; 

     IList<Customer> customers = new List<Customer>(); 

     foreach (IDataAccess nextDataAccess in dataAccess) 
     { 
      Customer customerDetail = nextDataAccess.GetDetails(lastName); 
      customers.Add(customerDetail); 
     } 

     return customers; 
    } 
} 

no he mostrado la inyección de dependencias que sea sencillo como su ya complicado conseguir ahora.

Ahora, si tenemos una nueva base de datos de detalles del cliente, podemos simplemente agregar una nueva clase que amplíe BaseDataAccess y proporcione su objeto de base de datos.

Por supuesto, necesitamos procedimientos idénticos almacenados en todas las bases de datos participantes.

Por último, el cliente para la clase CustomerServiceManager solo llamará al método GetCustomerDetails, pasará el apellido y no le importará cómo y dónde provienen los datos.

Espero que esto le dará un enfoque práctico para entender LSP.

+2

¿Cómo puede ser esto un ejemplo de LSP? –

+0

No veo el ejemplo LSP en eso tampoco ... ¿Por qué tiene tantos upvotes? – StaNov

+0

@RoshanGhangare IDataAccess tiene 3 implementaciones concretas que pueden ser sustituidas en Business Layer. –

Cuestiones relacionadas