Reputation: 136
I was looking for signs in code that can tell that Liskov's Substitution Principle can be potentially violated. First I made a simple class and another class inheriting from it:
public class Adder
{
public virtual int Add(int operand1, int operand2)
{
return operand1 + operand2;
}
}
public class AdvancedAdder : Adder
{
}
Then I created a UnitTest detecting LSP violation:
public abstract class AdderUnitTestsBase
{
protected static Adder Adder;
[DataTestMethod]
[DataRow(2, 2, 4)]
[DataRow(-1, 2, 1)]
[DataRow(2, -3, -1)]
[DataRow(0, 0, 0)]
public void Add_ReturnsCorrectResult(
int operand1, int operand2, int result)
{
Assert.AreEqual(result, Adder.Add(operand1, operand2));
}
}
[TestClass]
public class AdderUnitTests : AdderUnitTestsBase
{
[ClassInitialize]
public static void ClassInit(TestContext context)
{
Adder = new Adder();
}
}
[TestClass]
public class AdvancedAdderUnitTests : AdderUnitTestsBase
{
[ClassInitialize]
public static void ClassInit(TestContext context)
{
Adder = new AdvancedAdder();
}
}
Then I tried different things like changing type of parameters or changing number of parameters:
public class AdvancedAdder : Adder
{
public int Add(int operand1, int operand2, int operand3)
{
return operand1 + operand2 + operand3;
}
public uint Add(uint operand1, uint operand2)
{
return operand1 + operand2;
}
}
It did not violate LSP because the methods have different signatures. It just extended functionality. The only thing that worked is using the "override" key word:
public class AdvancedAdder : Adder
{
public override int Add(int operand1, int operand2)
{
return operand1 - operand2;
}
}
It looks like if I avoid "override" in C# I don't need to worry about possible Liskov's Substitution Principle violation. Do you think it's correct?
Upvotes: 2
Views: 685
Reputation: 76
It is also worth to mention that LSP should not be considered as a strict rule that you have to enforce in any situation.
The composite pattern for example (https://en.wikipedia.org/wiki/Composite_pattern) can provide you a mechanism to treat all nodes in an uniform way (which is good). But if you interpret LSP in a strict way it would violate LSP.
Like most of the object oriented rules and design patterns it is just a guideline that should point you in the right direction but may be overruled by other requirements. In the end the overall complexity of an architecture should be decreased and not increased. Here the composite pattern for example would allow you to treat objects in a more uniform way and provide simpler algorithms.
For your question itself. If you also consider messaging for object oriented design then you will also have categories of object that process a certain message/event then this is also a way to break LSP (instead of using only virtual and override).
In the end you should consider that the truth is always somewhere in the middle.
Upvotes: 0
Reputation: 11597
Overriding is just a technical solution to redefining a method. The LSP is more about semantics than technique.
It looks like if I avoid "override" in C# I don't need to worry about possible Liskov's Substitution Principle violation.
Maybe you mean polymorphism, because override
is just one way to change an objects behavior (dynamic binding). You can also use new
for non-virtual
methods (static binding). You can even use inspection (as a special case of reflection) and change behavior based on that:
public void method()
{
switch (this.getType().Name)
{
case "Square":
// do the square thing
break;
case "Rectangle":
// do the rectangle thing
break;
}
}
A method defined like that can very well break LSP without inheritance or override
/new
keywords.
It's really more complex than plain syntax, and more about semantics (meaning).
Even the classic LSP example with squares/rectangles is all about meaning. If you think that square/rectangle are just words without a semantic substrate you have no expectations about their behavior. As a result, you have no reason to think you can substitute one for the other and can't break LSP.
Inheritance, on the other hand, is a code construct that tells you:
PARENT
^
|
CHILD
The child can substitute the parent because it inherits all the behavior.
Upvotes: 5