Liskov Substitution Principle (LSP) in TypeScript
Liskov Substitution Principle (LSP)
I was recently asked to name some of “Uncle Bob’s” SOLID OOP design principles. One of my answers was Liskov Substitution Principle. I was told it is the least given answer, I wonder why?
Liskov Substitution Principle (wikipedia)
“objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
As I dive deeper into TypeScript I am looking for ways to keep my code SOLID. This is where the question that prompted this post surfaced. How do I maintain LSP in TypeScript?
LSP and TypeScript
Then it hit me. The most important part of LSP (to me) is maintaining intent or semantics. It doesn’t matter if we are talking about strong typing or replacing ducks with swans, how we subtype isn’t as important as the behavior of the subtype with respect to the parent. Does the subtype pass our expectation q?
The principle asks us to reason about what makes the program correct. If you can replace a duck with a swan and the program holds its intent, LSP is not violated. Yet, if you can replace a Mallard duck with a Pekin duck in a method that only expects Mallards and maintains the proper behavior only with Mallards, when you use a Pekin duck in the method you have broken LSP. Pekin violates LSP for the expectation we have for Mallard even if Pekin is a structurally correct subtype of Mallard, using Pekin changes the behavior of the program. Now if we have a Gadwal duck that is also a structurally correct subtype of Mallard and using it I observed the same behavior as using a Mallard, LSP isn’t violated and I am safe to use a Gadwal duck in place of a Mallard.
I believe LSP has merit in TypeScript and any programming languages in general. I believe that violation of LSP goes beyond throwing a “not implemented exception” or hiding members of the parent class. For example, if you have an interface that defines a method that should only return even numbers, then you have an implementation of the interface method that allows returning of odd numbers, LSP is violated. Pay attention to subtype behavior with respect to expectations of behavior.
Respecting the LSP in a program helps increase flexibility, reduces coupling and makes reuse a good thing, but it also helps reduce bugs and increase the value of a program when you pay attention to behavioral intent.
What do you think, should we care about LSP in TypeScript?