Liskov substitution principle (LSP or L in SOLID principles) in C#

What is Liskov Substitution Principle (LSP)?

The Liskov substitution principle (LSP) is a collection of guidelines for creating inheritance hierarchies in which a client can reliably use any class or subclass without compromising the expected behavior. If the rules of the LSP are not followed, an extension to a class hierarchy—that is, a new subclass—might necessitate changes to any client of the base class or interface. If the LSP is followed, clients can remain unaware of changes to the class hierarchy. As long as there are no changes to the interface, there should be no reason to change any existing code. The LSP, therefore, helps to enforce both the open/closed principle and the single responsibility principle.
The definition of the LSP by prominent computer scientist Barbara Liskov is a bit dry, so it requires further explanation. Here is the official definition:
Barbara Liskov: If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program.
There are three code ingredients relating to the LSP:
1- Base type – The type (T) that clients have reference to. Clients call various methods, any of which can be overridden—or partially specialized—by the subtype.
2- Subtype – Any one of a possible family of classes (S) that inherit from the base type (T). Clients should not know which specific subtype they are calling, nor should they need to. The client should behave the same regardless of the subtype instance that it is given.
3- Context – The way in which the client interacts with the subtype. If the client doesn’t interact with a subtype, the LSP can neither be honored nor contravened.

LSP rules

There are several “rules” that must be followed for LSP compliance. These rules can be split into two categories: contract rules (relating to the expectations of classes) and variance rules (relating to the types that can be substituted in code).

What are contract rules?
These rules relate to the contract of the supertype and the restrictions placed on the contracts that can be added to the subtype.
– Preconditions cannot be strengthened in a subtype.
– Postconditions cannot be weakened in a subtype.
– Invariants—conditions that must remain true—of the supertype must be preserved in a subtype.

To understand the contract rules, you should first understand the concept of contracts and then explore what you can do to ensure that you follow these rules when creating subtypes.

What are the variance rules?
These rules relate to the variance of arguments and return types.
– There must be contravariance of the method arguments in the subtype.
– There must be covariance of the return types in the subtype.
– No new exceptions can be thrown by the subtype unless they are part of the existing exception hierarchy.

The concept of type variance in the languages of the Common Language Runtime (CLR) of the Microsoft .NET Framework is limited to generic types and delegates. However, variance in these scenarios is well worth exploring and will equip you with the requisite knowledge to write code that is LSP compliant for variance.

Open closed Principle (OCP or O in SOLID principles) in C#

What is Open closed Principle?

In 1988 the open closed principle (OCP) mentioned by Bertrand Meyer as: Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
Software that works should when possible not be changed when your application is extended with new functionality.
Instead it should be possible to extend the existing software with new functionality without any modification to the current codebase and without adding duplicate code or duplicate functionality.
The OCP handles the way you want to structure your code in order to yield one of the greatest benefits claimed for object oriented technology: reusability and maintainability.
The best way to implement the open closed principle is to first start with implementing the Single Responsibility Principle: a class should have one, and only one, reason to change. This will separate different concerns in your code.
The next step is represent these separate concerns by abstractions and let consumers of these concerns talk to these abstractions.
To state the open closes principle very straightforward way you can say :
– You should design modules that never change.
– When requirements change, you extend the behavior of such modules by adding new code, not by changing old code that already works.
Abstraction is the way to realize this principle.
Derivatives from an abstraction are closed for modification because the abstraction is fixed but behaviour can be extended by creating new derivatives of the abstraction.

Let’s continue this discussion with the new world of client, server and Web APIs. The open closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”.  For the sake of this post I will use the term “server” to describe a class in an API – a business object for example.  “Client” will be used to describe the consumer of a service – it could be a user interface component for example.  The client has a dependency on a server at some level, the server knows nothing about a client.

Let’s paraphrase what Bertrand Meyer said about this principle. I am paraphrasing his definition as stating that once a class is completed it could only be modified to fix errors.  If you wanted to add any new features or change the behavior in any way you had to create a new class.  Inheritance may be used to reuse code from the original class but the resulting interface of the new class need not be the same as the interface of the original class.  Consider client class A that depends on server class B.  Once A and B are working they would never change.  Now we create server C which is an extension of B (reuses implementation but may have a different interface).  We have extended the API by creating server C but we did not change A or B so A continues to behave correctly.  The focus here is on how we can extend our API but keep clients working by not changing any of the working code.

Robert Martin is credited with popularizing the more modern application of the principle which advocates the use of abstract interfaces.  With this approach, server implementations can be changed, and multiple server implementations can be created and substituted for each other in the client.  Again consider client class A that depends on server class B but this time B is an abstract class.  Now we create server class C which is a derivation of B.  Since client A is dependent on an abstraction, we can extend the client by passing it C without changing A.  The focus here is on opening the client to extension but closing the client to modification.

Meyer’s approach is better in that it shouldn’t be possible to break working client A by adding server derivation C.  The down side, however, is that our answer to all change is to add more code, client A will not be able to make use of server extension C, and refactoring of B is not allowed.  With Martin’s approach we are essentially saying “as long as we respect the interface, never mind what I do to the server”.  The down side here is that it is easy to imagine breaking client A by adding a poorly designed server derivation C or even by carelessly refactoring B and inadvertently adding a bug.  The upside is that with this approach, client A may be extended to make use of server C  without changing the client and since we have the freedom to refactor our servers we should have less code, we can make use of new language features where it makes sense, and we can reorganize things as our API evolves.  In short the resulting code should be easier to maintain.

Copyright © All Rights Reserved - C# Learners