Search code examples
c#inheritanceliskov-substitution-principle

How Liskov substitution principle is different from normal inheritance?


I am trying to understand Liskov substitution principle. but I am not able to identify How Liskov substitution principle is different from normal inheritance. Below code is about normal inheritance. What should I do to below code to say My code followed Liskov substitution principle

    public class ClassA
    {
        public virtual void MethodA()
        {
            Console.WriteLine("-----------------ClassA.MethodA---------------------");
        }

        public virtual void MethodB()
        {
            Console.WriteLine("-----------------ClassA.MethodB---------------------");
        }
    }

    public class ClassB: ClassA
    {
        public override void MethodA()
        {
            Console.WriteLine("-----------------ClassB override ClassA.MethodA---------------------");
        }
    }

Solution

  • Here's a common definition:

    https://www.tomdalling.com/blog/software-design/solid-class-design-the-liskov-substitution-principle/

    The Liskov Substitution Principle (LSP): functions that use pointers to base classes must be able to use objects of derived classes without knowing it.

    Here's a more rigorous explanation:

    https://en.wikipedia.org/wiki/Liskov_substitution_principle

    ..the Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called (strong) behavioral subtyping, that was initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy. It is a semantic rather than merely syntactic relation, because it intends to guarantee semantic interoperability of types in a hierarchy, object types in particular. Barbara Liskov and Jeannette Wing described the principle succinctly in a 1994 paper as follows:

    Subtype Requirement:

    Let ϕ ( x ) be a property provable about objects x of type T. Then ϕ ( y ) should be true for objects y of type S where S is a subtype of T.

    Liskov's principle imposes some standard requirements on signatures that have been adopted in newer object-oriented programming languages (usually at the level of classes rather than types; see nominal vs. structural subtyping for the distinction):

    • Contravariance of method arguments in the subtype.
    • Covariance of return types in the subtype.
    • No new exceptions should be thrown by methods of the subtype, except where those exceptions are themselves subtypes of exceptions thrown by the methods of the supertype.

    In addition to the signature requirements, the subtype must meet a number of behavioural conditions. These are detailed in a terminology resembling that of design by contract methodology, leading to some restrictions on how contracts can interact with inheritance:

    • Preconditions cannot be strengthened in a subtype.
    • Postconditions cannot be weakened in a subtype.
    • Invariants of the supertype must be preserved in a subtype.
    • History constraint (the "history rule"). Objects are regarded as being modifiable only through their methods (encapsulation). Because subtypes may introduce methods that are not present in the supertype, the introduction of these methods may allow state changes in the subtype that are not permissible in the supertype. The history constraint prohibits this.

    Q: Does your example conform?

    A: I don't think so. Because A.MethodA() is semantically "different" from B.MethodA(). ClassB doesn't pass The Duck Test.

    I propose this counter-example:

    public class ClassA {
      public virtual void MethodA() {
        Console.WriteLine("ClassA.MethodA");
      }
    
      public virtual void MethodB(){
        Console.WriteLine("ClassA.MethodB");
      }
    }
    
    public class ClassC: ClassA {
      public void MethodC() {
        Console.WriteLine("ClassC.MethodC");
      }
    }
    

    This is also an excellent example of why LSP is NOT "the same as" inheritance.