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.
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.
// 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);
}
}
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);
}
}, [])
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.
//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;
}
As you can see, in the GetSomethingById
method we get data from repository, and we update the reply property using DownloadString
method. 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)
{
}
}
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
}
}
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)