2012-02-27 17 views
6

me gustaría hacer referencia al ejemplo que se utilizó antes de SO con el pato y el pato eléctrica:C# es la interfaz que echa una violación del principio de sustitución de liskov

public interface IDuck 
{ 
    void Swim(); 
} 

public class Duck : IDuck 
{ 
    public void Swim() 
    { 
     //do something to swim 
    } 
} 

public class ElectricDuck : IDuck 
{ 
    public void Swim() 
    { 
     if (!IsTurnedOn) 
      return; 

     //swim logic 
    } 

    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 
} 

La violación original para LSP se vería de esta manera:

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

Una solución por el autor fue poner la lógica dentro método de natación del pato eléctrica para que se encienda:

public class ElectricDuck : IDuck 
{ 
    public void Swim() 
    { 
     if (!IsTurnedOn) 
      TurnOn(); 

     //swim logic 
    } 

    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 
} 

me he encontrado con otros escenarios donde una interfaz extendida puede ser creado que admite algún tipo de inicialización:

public interface IInitializeRequired 
{ 
    public void Init(); 
} 

pato eléctrico podría ampliarse con esta interfaz:

public class ElectricDuck : IDuck, IInitializeRequired 
{ 
    public void Swim() 
    { 
     if (!IsTurnedOn) 
      return; 

     //swim logic 
    } 

    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 

    #region IInitializeRequired Members 

    public void Init() 
    { 
     TurnOn(); 
    } 

    #endregion 
} 

EDITAR : El motivo de la interfaz extendida se basa en que el autor dice que encenderse automáticamente en el método de nado podría tener otros resultados no deseados.

A continuación, el método en vez de comprobar y echando a un tipo específico puede buscar una interfaz extendida en su lugar:

void MakeDuckSwim2(IDuck duck) 
    { 
     var init = duck as IInitializeRequired; 
     if (init != null) 
     { 
      init.Init(); 
     } 

     duck.Swim(); 
    } 

El hecho de que hice el concepto de inicialización más abstracto a continuación para crear una interfaz extendida llamada IElectricDuck con El método TurnOn(), puede hacer que parezca que hice lo correcto, sin embargo, todo el concepto Init puede existir solo debido a un pato eléctrico.

¿Es esta una mejor forma/solución o es solo una violación de LSP disfrazada?

Gracias

+0

¿Por qué no reducir aún más el problema con otra generalización? Brevemente, por ejemplo: 'IDuck.HasEnergy', etc., puede aplicar 'obtener energía', que será el mismo en principio para cada pato, pero diferente en detalles (uno podría comer, el otro insertar baterías o encenderse). –

+0

Estaba pensando en estas líneas, pero queda la pregunta de que el método no toma un argumento para estas interfaces, sino más bien en el nivel más alto y luego realiza fundición de interfaz, esto podría causar los mismos problemas de violación LSP cuando se agregan otras generalizaciones, por ejemplo, CanGetEnergy() de repente ahora todos los métodos necesitarían agregar esta lógica – Andre

+0

¿Cuál es el problema para poner la lógica dentro del método de nado eléctrico del pato para encenderse? –

Respuesta

4

Me gustaría volver a considerar su último ejemplo como una violación LSP debido lógicamente a hacer exactamente esto. Como dijiste, no hay un concepto de inicialización realmente, solo se compone de un truco.

De hecho, su método MakeDuckSwim no debería saber nada sobre las características de un pato (si debe inicializarse primero, alimentarse con algún destino después de la inicialización, etc.). ¡Solo tiene que hacer el pato proporcionado!

Es difícil de ver en este ejemplo (ya que no es real), pero parece que en algún lugar "superior" hay una fábrica o algo que te crea un pato específico.

¿Es posible que te pierdas el concepto de una fábrica aquí?

Si había uno, entonces Se debe saber lo que el pato está creando exactamente lo que probablemente que debe ser responsable de saber cómo inicializar un pato, y el resto de su código sólo funciona con iDuck sin ningún " ifs "dentro de los métodos de comportamiento.

Obviamente, puede introducir el concepto de "inicialización" directamente en la interfaz IDuck.Diga, un pato "normal" necesita ser alimentado, uno eléctrico necesita ser encendido, etc. :) Pero suena un poco dudoso :)

+0

Hola Alexey, gracias por tu respuesta, ¿qué opinas de las veces en que tu método usa una interfaz pero también necesita realizar algo adicional si hay una interfaz extendida, por ejemplo IDisposable si necesitas para tirar el objeto, no hay otra manera que echar y si es desechable y hacer un desecho? Ese es el ejemplo más simple en el que podría pensar – Andre

+0

@Andre Disposable es una historia diferente. Desechable es _technical_, not _functional_. En otras palabras, Desechable no tiene nada que ver con la lógica comercial. Lo mismo para IComparable, IEquatable, etc. No representan comportamientos desde el punto de vista de la lógica de negocios. Incluso allí, cuando se trata de productos desechables, lo único que necesita es una interfaz IDisposable, no le importa lo que es de facto. –

0

Creo que primero tiene que responder esta pregunta acerca de los patos eléctricos - do se encienden automáticamente cuando alguien les pide que naden? Si es así, enciéndalos en el método Swim.

Si no, es responsabilidad del cliente del pato encenderlo, y también podría arrojar un InvalidOperationException si el pato no puede nadar porque está apagado.

5

Es una violación LSP disfrazada. Su método acepta un IDuck, pero requiere verificación del tipo dinámico (si el IDuck implementa IInitializeRequired o no) para funcionar.


Una posibilidad para solucionar este problema sería aceptar el hecho de que algunos patos requieren inicialización y redefinir la interfaz:

public interface IDuck 
{ 
    void Init(); 

    /// <summary> 
    /// Swims, if the duck has been initialized or does not require initialization. 
    /// </summary> 
    void Swim(); 
} 

Otra opción es aceptar que un ElectricDuck sin inicializar no es realmente un pato; Por lo tanto, no implementa iDuck:

public class ElectricDuck 
{ 
    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 

    public IDuck GetIDuck() 
    { 
     if (!IsTurnedOn) 
      throw new InvalidOperationException(); 

     return new InitializedElectricDuck(); // pass arguments to constructor if required 
    } 

    private class InitializedElectricDuck : IDuck 
    { 
     public void Swim() 
     { 
      // swim logic 
     } 
    } 
} 
+0

Hola, Heinzi, este parece ser un mejor enfoque, pero ¿cómo relacionarías esto con otras formas de conversión de interfaz, por ejemplo, en .NET donde tienes algunas referencias de interfaz y en Disposal to var disp = (Someinterface as IDisposable)? – Andre

+0

@Andre: Por lo general, el código que crea el objeto es responsable de eliminarlo (mediante el uso de la declaración 'using'), así que supongo que esta es una situación bastante rara. – Heinzi

0
public interface ISwimBehavior 
{ 
    void Swim(); 
} 

public interface IDuck 
{ 
    void ISwimBehavior { get; set; } 
} 

public class Duck : IDuck 
{ 
    ISwimBehavior SwimBehavior { get { return new SwimBehavior(); }; set; } 
} 

public class ElectricDuck : IDuck 
{ 
    ISwimBehavior SwimBehavior { get { return new EletricSwimBehavior(); }; set; } 
} 

Las clases de comportamiento:

public class SwimBehavior: ISwimBehavior 
{ 
    public void Swim() 
    { 
     //do something to swim 
    } 
} 

public class EletricSwimBehavior: ISwimBehavior 
{ 
    public void Swim() 
    { 
     if (!IsTurnedOn) 
      this.TurnOn(); 

     //do something to swim 
    } 

    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 
} 
+2

¿No veo cómo esto ayuda? ¿Puedes agregar algunos comentarios por favor? –

0

tal vez algo como esto:

public interface IDuck 
{ 
    bool CanSwim { get; } 
    void Swim(); 
} 

public class Duck : IDuck 
{ 
    public void Swim() 
    { 
     //do something to swim 
    } 

    public bool CanSwim { get { return true; } } 
} 

public class ElectricDuck : IDuck 
{ 
    public void Swim() 
    { 
     //swim logic 
    } 

    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 
    public bool CanSwim { get { return IsTurnedOn; } } 
} 

cliente sería cambiado como:

void MakeDuckSwim(IDuck duck) 
{ 
     if (duck.CanSwim) 
     { 
      duck.Swim(); 
     } 
} 
Cuestiones relacionadas