DEV Community

Emin Vergil
Emin Vergil

Posted on • Updated on

What is Single Responsibility Principle ?

SOLID is an acronym is invented by Uncle Bob, so we can remember these core concepts for clean coding better. The first letter stands for Single Responsibility Principle (SRP). SRP says that a class or a function should be responsible only for one thing.

SRP in Functional Programming

The SRP says one class or one function should be responsible only for one thing. For instance, if we have a function that is responsible for reading a data and mutating another data, then there are two reasons to change. The reasons are, if the implementation of reading a data from a database or mutating a data changes, we need to modify the whole function.

For this kind of situations, there is interface in object-oriented programming languages. But for functional programming generally there is no implementation like that because we don't use classes in functional programming languages(FPL).

Note: There is interface implementation in Typescript, so if you 
use Typescript for FPL, you can create an interface for 
SRP.
Enter fullscreen mode Exit fullscreen mode

In the example below, you can see the bad implementation of a function where it violates the SRP.

Note: I'll use the React library to demonstrate the concept.
Enter fullscreen mode Exit fullscreen mode
// bad example
let handleFetching = async () => {

    // 1. read data from somewhere
    let res = await fetch("some.url").then(res => res.json())

    if(res != null)
    {
        //2. mutate the state
        setData(res.data);
    }

}

Enter fullscreen mode Exit fullscreen mode

As you can see in the handleFetching function, there are two responsibilities. The first one is reading data from somewhere and the second one is mutating some state or data. So if the implementation changes for these two different responsibilities, we need to change handleFetching function. Also, it makes hard to test this function where we need to test these two responsibilities.

// good example
let handleFetching = async () => {

    // 1. read data from somewhere
    let res = await getData();
    return res
}

let getData = async () => {
// get data implementation goes here
}

useEffect(() => {
    let res = handleFetching()
    if(res != null)
    {
        //2. mutate the state
        setData(res.data);
    }
}, [])

Enter fullscreen mode Exit fullscreen mode

As you can see in the good example, we made every function responsible for only one thing. The handleFetching function now responsible only for fetching data and not mutating some state. The getData is responsible for getting data from somewhere etc. So we separated all responsibilities to the different functions, so we can test them separately.

SRP in OOP

Similar to functional programming, we can analyze a function to determine it is responsible for one thing or not. If there are multiple responsibilities for a function or a service, we should separate those responsibilities.

In the code below, you can see the bad example.

Note: I'll use c# programming language to demonstrate the concept.
Enter fullscreen mode Exit fullscreen mode
//bad example
public async Task<Something> GetSomethingById(int id)
{
    var something = await _somethingRepo.GetOneAsync(x => x.id == id);

    // violates the SRP 
    var url = "google.com";
    var client = new WebClient();
    var reply = client.DownloadString(url);
    something.reply = reply;


} 
Enter fullscreen mode Exit fullscreen mode

As you can see, in the GetSomethingById method we get data from repository, and we update the reply property using DownloadStringmethod. So there are two different responsibilities for this function, one is getting data, the other reason is mutating a property.

// bad example
class FoodDelivery
{
    private List<Food> foods;

    public Food MakeFood(string name)
    {

    }

    public Food GetFood(int id, int price)
    {

    }

    public void DeliverFood(string path)
    {

    }

}
Enter fullscreen mode Exit fullscreen mode

In this example, we can see that FoodDelivery class has three different methods and all of them are responsible from different logic. If we change an implementation from any of them, it could affect other functions. For example, if we change MakeFood method, maybe we need to update some logic inside DeliverFood method. And also MakeFood and GetFood functions don't relate to food delivery business. So let's see how can we turn this bad example to the good one by separating logic to different services.

//good example
public interface IFoodGetter{
    Food GetFood(int id, int price);
}


public interface IFoodMaker{
    Food MakeFood(string name);
}

public class FoodGetter : IFoodGetter {
    public Food GetFood(int id, int price){
        //implementation goes here
    }
}

public class FoodMaker : IFoodMaker {
    public Food MakeFood(string name){
        //implementation goes here
    }
}

class FoodDelivery
{
    private List<Food> foods;
    private readonly IFoodGetter _foodGetter;
    private readonly IFoodMaker _foodMaker;

    public FoodDelivery(IFoodGetter foodGetter, IFoodMaker foodMaker){
        foodGetter = _foodGetter;
        foodMaker = _foodMaker;
    }

    public void DeliverFood(string path)
    {
        var food = _foodGetter.GetFood(1,20);

        if(food == null){
            food = _foodMaker.MakeFood("food_name");
        }

        //delivery implementation goes here

    }

}
Enter fullscreen mode Exit fullscreen mode

In the example above, we created two different services such as FoodGetter, FoodMaker to separate business logic. And FoodDelivery class is only responsible for delivering food. If we need to change some implementation in FoodGetter service, we can change it inside that service. And also using interfaces we can create mock implementation of those services so we can test them without connecting to real database etc.

Top comments (0)