C # Is the interface a violation of the Liskov Substitution Principle

I would like to refer to the example that was used earlier in https://stackoverflow.com/a/4129/64 with the duck and electric duck:

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; } } 

The original violation for the LSP would look like this:

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

One of the author’s decisions was to incorporate logic into the electric duck duck method:

 public class ElectricDuck : IDuck { public void Swim() { if (!IsTurnedOn) TurnOn(); //swim logic } public void TurnOn() { this.IsTurnedOn = true; } public bool IsTurnedOn { get; set; } } 

I came across other scenarios where an advanced interface can be created that supports some initialization:

 public interface IInitializeRequired { public void Init(); } 

The electric weft can then be expanded using this interface:

  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 } 

EDIT: The reason for the extended interface is based on the fact that the author says that automatic inclusion in the swimming method may have other undesirable results.

Then, instead of checking and dropping a specific type, you can look for an advanced interface:

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

The fact that I made the initialization concept more abstract, and then created an advanced interface called IElectricDuck with the TurnOn () method, can make it do the right thing, but the whole Init concept can only exist because of the electric duck.

Is this the best way / solution or is it just breaking the LSP in disguise.

thanks

+6
source share
5 answers

I would still consider your last example as an LSP violation, because logically you are doing just that. As you said, there really is no concept of initialization, it is simply composed as a hack.

In fact, your MakeDuckSwim method should not know anything about any duck specifications (should you initialize it first, feed it with some destination after initialization, etc.). It just needs to make a floating duck!

It is hard to say on this example (as it’s not realistic), but it looks somewhere “on top”, is there a factory or something that creates a certain duck for you.

Is it possible that you missed the factory concept here?

If there was one, then It should know which duck it creates in this way, probably it should be responsible for how to initialize the duck, and the rest of your code just works with IDuck without any "ifs" inside the behavioral methods.

Obviously, you can introduce the concept of “initialization” directly to the IDuck interface. Say, a “normal” duck needs to be fed, an electric duck needs to be turned on, etc. :) But that sounds a bit dodgy :)

+4
source

This is a violation of the LSP in disguise. Your method accepts IDuck , but it requires dynamic type confirmation (whether IDuck implements IInitializeRequired or not).


One way to fix this would be to agree that some ducks require initialization and redefinition of the interface:

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

Another option is to accept that an uninitialized ElectricDuck is not really a duck; thus, it does not implement 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 } } } 
+5
source

I think first you need to answer this question about electric ducks - do they automatically turn on when someone asks them to swim? If so, enable them in the Swim method.

If not, then the responsibility for inheriting the duck from the client is to simply throw an InvalidOperationException if the duck cannot swim, because it is disabled.

0
source
 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; } } 

Behavior Classes:

 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; } } 
0
source

Maybe something like this:

 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; } } } 

The client will be changed as follows:

 void MakeDuckSwim(IDuck duck) { if (duck.CanSwim) { duck.Swim(); } } 
0
source

Source: https://habr.com/ru/post/909448/


All Articles