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.”

TypeScript

If you are a serious developer that stays up to date with the profession, you know what TypeScript is by now. TypeScript is a superset of JavaScript that provides type checking based on the structure or shape of values at compile time and at design time if your IDE supports it. This structural type checking is sometimes referred to as duck typing. If it looks like and quacks like the type, its the type.

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

When I subtype an object in JavaScript its not the same inheritance that we have in Java or C#. In JavaScript I can have function B stand in for A as long as function B has the same structure, which is different than static type checking. TypeScript provides syntactic sugar on top of JavaScript to enforce type constraints, but this doesn’t change the dynamic typing of JavaScript. In fact, I can write plain JavaScript in TypeScript so how does LSP apply when subtypes are a different animal in JavaScript as opposed to a statically typed OOP based language.

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.

Conclusion

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.