Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I would like to refer to the example that was used before on SO 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 LSP would look like this:

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

One solution by the author was to put the Logic inside the electric duck's swim method to turn itself on:

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

        //swim logic  
    }

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

    public bool IsTurnedOn { get; set; }
}

I have come across other scenarios where an extended interface can be created that supports some sort of initialization:

public interface IInitializeRequired
{
    public void Init();
}

Electric Duck could then be extended with 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 author saying that turning on automatically in the swim method might have other undesired results.

Then the method instead of checking and casting to a specific type can look for an extended interface instead:

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 then to create an extended interface called IElectricDuck with TurnOn() method, may make this seem that I did the right thing, however the whole Init concept may only exist because of electric duck.

Is this a better way/solution or is this just an LSP violation in disguise.

Thanks

like image 250
Andre Avatar asked Feb 27 '12 12:02

Andre


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr. Stroustroupe.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

How old is the letter C?

The letter c was applied by French orthographists in the 12th century to represent the sound ts in English, and this sound developed into the simpler sibilant s.


2 Answers

It's an LSP violation in disguise. Your method accepts an IDuck, but it requries verification of the dynamic type (whether the IDuck implements IInitializeRequired or not) to work.


One possibility to fix this would be to accept the fact that some ducks require initialization and redefine 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
        }
    }
}  
like image 153
Heinzi Avatar answered Oct 05 '22 23:10

Heinzi


I would still consider your final example as an LSP violation because logically you do exactly this. As you said, there is no concept of initialization really, it is just made up as a hack.

Indeed, your MakeDuckSwim method should not know anything about any duck's specifics (whether it should be initialized first, fed with some destination after initialization, etc). It just has to make the provided duck swim!

It is hard to tell on this example (as it is not real), but looks like somewhere "upper" there is a factory or something that creates you a specific duck.

It it possible that you miss the concept of a factory here?

If there was one, then It should know what duck it is creating exactly so probably it should be responsible to know how to initialize a duck, and the rest of your code just works with IDuck without any "ifs" inside behavioral methods.

Obviously you can introduce the concept of "initialization" straight to IDuck interface. Say, a "normal" duck needs to be fed, an electrical one needs to be turned on, etc :) But it sounds a bit dodgy :)

like image 39
Alexey Raga Avatar answered Oct 06 '22 00:10

Alexey Raga