SOLID Principle

Amit Gupta
5 min readDec 12, 2020

Hi Guys , I am keeping SOLID principle very simple and easy here so that we can understand it better where do we use it in our daily work whether he/she is working on Java/Kotlin, it will give you very generic understanding about the SOLID Principle and you will feel this is very important in our daily work. I am breaking down every letter to make it understand in better way.

S- Single Responsibility

MVVM(View , ViewModel, Model) Architecture is Single Responsibility Principle since all 3 layers means View Layer, Logic Layer and Data layer have different responsibilities.

Different classes for Different Responsibility

O- Open for Extension and Closed for Modification.

Suppose you are sending some Transactional Push Notifications to the users. Initially we consider Payment as a transaction and later we found that we are having use cases for Refund , Settlement then we should not modify our class instead we should extend the transaction class and use inheritance for supporting new kinds of transactions. So we can use factory Design Pattern here which will allow us to follow this principle.

public interface NotificationProcessor {

void processPayload(String payload);

int getNotificationId();


}
public class TxnNotificationProcessor implements NotificationProcessor {

@Override
public void processPayload(String payload) {
// put default implementation.....

}

@Override
public int getNotificationId() {
return NotificationIdGenerator.nextID();
}
}
public class PaymentNotificationProcessor extends TxnNotificationProcessor {

@Override
public void processPayload(String payload) {
// put Payment as txn implementation.....

}
public String getTxnAmount(){
// It will be full Payment Amount without any Tax deuction
}
}

So Above code is open for Extension and closed for the modification and follow the above Principle.

L- Liskov Substitution Principle: Derived classes should be substitutable for Parent Class.

Square is not Rectangle if sides are passed with different values

In mathematics, a Square is a Rectangle. Indeed it is a specialisation of a rectangle. The "is a" makes you want to model this with inheritance. However if in code you made Square derive from Rectangle, then a Square should be usable anywhere you expect a Rectangle. This makes for some strange behaviour.

Imagine you had SetWidth and SetHeight methods on your Rectangle base class; this seems perfectly logical. However if your Rectangle reference pointed to a Square, then SetWidth and SetHeight doesn't make sense because setting one would change the other to match it and give wrong result for area. In this case Square fails the Liskov Substitution Test with Rectangle and the abstraction of having Square inherit from Rectangle is a bad one. I am taking another Example to understand it better since many post use the same example of Rectangle and Square.

class Program
{
static void Main(string[] args)
{
Apple apple = new Orange();
Debug.WriteLine(apple.GetColor());
}
}

public class Apple
{
public virtual string GetColor()
{
return "Red";
}
}
public class Orange : Apple
{
public override string GetColor()
{
return "Orange";
}
}

Here Orange is not an Apple but both are category of Fruits. If we are initialising object like: Apple apple = new Orange(); now for apple.getColor() we will get Orange colour since it is overridden by Orange object reference, But Apple can’t be Orange so here we are forcefully overriding those properties of Apple using Orange which are not actual property of Apple but both will be fit if Fruit class is inherited for Apple and Orange.

class Program
{
static void Main(string[] args)
{
Fruit fruit = new Orange();
Debug.WriteLine(fruit.GetColor());
fruit = new Apple();
Debug.WriteLine(fruit.GetColor());
}
}
public abstract class Fruit
{
public abstract string GetColor();
}
public class Apple : Fruit
{
public override string GetColor()
{
return "Red";
}
}
public class Orange : Fruit
{
public override string GetColor()
{
return "Orange";
}
}
}

I- Interface Segregation Principle

All the interfaces should be segregated with cohesive responsibility.

https://dzone.com/articles/solid-principles-by-example-interface-segregation

Printer can have functionality for FAX, Scan and Print but all not mandatory so segregate them in different interfaces will be more efficient if in future any new Print Technology is introduced or older one is removed.

D- Dependency Inversion Principle

“High-level modules should not depend on low-level modules. Both should depend on abstractions.”

“Abstractions should not depend on details. Details should depend on abstractions.”

It means You should inject dependencies through Abstraction in a class so that in future all new concrete classes can be powered through same Abstraction.

For Example We have Repository Class which provides data to the UI Layer through different Data Source like from Remote Backend Service , from Cache Data so We should inject data source in form of Dependency injection in constructor of Repository class . What will be the benifit of this, if we want to replace our Cache data source with Database we can add this layer without changing in Repository classes.

open class TrendingRepository @Inject constructor(
@com.example.hotrepo.di.scopes.RepoRemoteDataSource
private val remoteDataSource: RepoDataSource,
@com.example.hotrepo.di.scopes.RepoLocalDataSource
private val localDataSource: RepoDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
): BaseRepoRepository {
override suspend fun getTrendingRepoList(): Flow<List<TrendingRepoEntity>> =
localDataSource.getTrendingRepoList().flowOn(ioDispatcher)

override suspend fun doNetworkJob() : Resource<List<TrendingRepoEntity>?> {
return performNetworkOperation(
networkCall = { remoteDataSource.getNetworkRepoList() },
saveCallResult = { localDataSource.saveAndDeleteRepoList(it)}
)
}
}

Both RepoLocalDataSource class and RepoRemoteDataSource class give data to Logic layer but their source is different and hence they are injected in Repository through abstract interfaces and in future their implementation class may have more functionality.

Here Repository and DataSource both are using abstraction and that abstraction is injected through dependency injection.

I hope it will be helpful for the reader of this post. Please do clap if you liked it.

--

--