History
In our previous posts, we learned ‘What is S.O.L.I.D. Programing Principles’ and a detailed explanation with code of Single Responsibility Principle and Open/closed Principle.
S.O.L.I.D. is an acronym introduced by Michael Feathers as:
- S for SRP: Single responsibility principle
- O for OCP: Open/closed principle
- L for LSP: Liskov substitution principle
- I for ISP: Interface segregation principle
- D for DIP: Dependency inversion principle
- Single Responsibility Principle says, class should have single responsibility. In reference to this, I would say “A class should have single responsibility”.
Let's dive into ocean – can we read this like “a class should not design to do multiple activities”. - Open/Closed Principle says, “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”.
Learning S.O.L.I.D is a very vast topic and it is not possible to explore it in one-shot. I divided this into the following parts:
Introduction
In this whole article, we will learn Liskov Substitution Principle in details with an example.
Here is the definition from wiki:
“if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may be substituted for objects of type T)
without altering any of the desirable properties of that program (correctness, task performed, etc.)”
Learning Liskov Substitution Principle (LSP)
I understood the above definition like this: parent should easily replace the child object.
Let's explore this with an example: Go back to code discussed during our previous session and suppose we have several databases, some of these should not be migrated.
Take a look into the following snippet:
public class DataBase
{
public virtual bool IsValid(ServerData data, SourceServerData sourceData)
{
return new Validator(new List<IValidator>()).Validate(data, sourceData);
}
public virtual void Save()
{
}
}
public class ProdDB : DataBase
{
public override bool IsValid(ServerData data, SourceServerData sourceData)
{
return base.IsValid(data, sourceData);
}
public override void Save()
{
base.Save();
}
}
public class QADB : DataBase
{
public override bool IsValid(ServerData data, SourceServerData sourceData)
{
return base.IsValid(data, sourceData);
}
public override void Save()
{
base.Save();
}
}
public class LocalDB : DataBase
{
public override bool IsValid(ServerData data, SourceServerData sourceData)
{
return base.IsValid(data, sourceData);
}
public override void Save()
{
throw new Exception("Local Data should not be saved!");
}
}
Recall, inheritance and you can visualize that DataBase
is a parent class of ProdDB
, QADB
and LocalDB
.
Let's think polymorphism for a while and we can write as:
DataBase pDataBase = new ProdDB();
DataBase qDataBase = new QADB();
DataBase lDataBase = new LocalDB();
var dataBases = new List<DataBase> {new ProdDB(), new QADB(), new LocalDB()};
Isn’t it easy to save my object using this:
var dataBases = new List<DataBase> {new ProdDB(), new QADB(), new LocalDB()};
foreach (var dataBase in dataBases)
{
if (dataBase.IsValid(data, sourceData))
dataBase.Save();
}
Wait, wait…
What's wrong in the above, NOTHING?
Yes, you are absolutely correct. There is nothing wrong with the above code, the only thing is, its execution. When the above code executes, it will also invoke save
method of LocalDB
object. In this case, we received an exception as our LocalDB
object is not supposed to save data.
A big question is “why this happened?”
In simple words, LocalDB
is actually not an entity of DataBase
or we can say DataBase
is not an actual parent of LocalDB
.
Another question in mind “Why LocalDB
is not an entity of DataBase
, when it inherits DataBase
“.
Hold on, go back to LocalDB
class and check this is not meant to implement Save()
method, here this makes LocalDB
as a separate entity.
In simple words, LISCOV says parent should easily replace its child.
How to Implement LISCOV Principle?
We know LocalDB
is not supposed to save data, but others are. Let's consider the following snippet:
public interface IRule
{
bool IsValid(ServerData data, SourceServerData sourceData);
}
public interface IRepository
{
void Save();
}
Now, we have two interfaces, with their own methods. IRule: to validate data and IRepository: to save/persist data.
Let's make changes to our LocalDB
class, as:
public class LocalDB : IRule
{
public bool IsValid(ServerData data, SourceServerData sourceData)
{
return new Validator(new List<IValidator>()).Validate(data, sourceData);
}
}
Why We Implement IRule?
For LocalDB
, we only want to check whether data is valid or not. We do not want to persist data in any scenario.
Now, we can’t write this:
DataBase lDataBase = new LocalDB();
Our DataBase
class should be like this:
public class DataBase : IRule, IRepository
{
public virtual bool IsValid(ServerData data, SourceServerData sourceData)
{
return new Validator(new List<IValidator>()).Validate(data, sourceData);
}
public virtual void Save()
{
}
}
Other classes will remain unchanged.
Our execution code goes as:
public void Execute(ServerData data, SourceServerData sourceData)
{
var dataBases = new List<IRepository> { new ProdDB(), new QADB() };
foreach (var dataBase in dataBases.Where(dataBase => ((IRule)dataBase).IsValid(data, sourceData)))
{
dataBase.Save();
}
}
Now, our code looks much easier to handle. 
How to Download the Source Code?
You can download the complete souce code of examples used in this article from GitHub: Learning Solid.
The post Learning The S.O.L.I.D Programming Principles: Liskov substitution principle [Part – IV] appeared first on Gaurav-Arora.com.