Single Responsibility Principle
A class should have only one reason to change. In simple terms SRP states that, a module or class should not be overloaded with multiple responsibilities and a single responsibility shouldn't be spread across multiple classes or mixed with other responsibilities. It is the necessary and sufficient condition for good code. If a class has single responsibility then it is likely to be very robust. Its much easier to implement, maintain and verify its working as per logic defined thus much easier to write unit test cases.
Example
Without SRP
Consider a DataRecords class as shown below which has four functionalities : Add the data, delete the data, send email out and log any debugging messages.
public class DataRecords
{
public long StrData { get; set; }
public DateTime Dt { get; set; }
public void AddData()
{
try
{
// Code for adding data
// Once the data has been added, then send the mail
MailMessage mailMessage = new MailMessage("EMailFrom", "EMailTo", "EMailSubject", "EMailBody");
this.SendDataEmail(mailMessage);
}
catch (Exception ex)
{
System.IO.File.WriteAllText(@"c:\ErrorLog.txt", ex.ToString());
}
}
public void DeleteData()
{
try
{
// Code for Deleting the Data
// Once the data has been deleted, then send the mail
MailMessage mailMessage = new MailMessage("EMailFrom", "EMailTo", "EMailSubject", "EMailBody");
this.SendDataEmail(mailMessage);
}
catch (Exception ex)
{
System.IO.File.WriteAllText(@"c:\ErrorLog.txt", ex.ToString());
}
}
public void SendDataEmail(MailMessage mailMessage)
{
try
{
//Code for sending the email
}
catch (Exception ex)
{
System.IO.File.WriteAllText(@"c:\ErrorLog.txt", ex.ToString());
}
}
}
With SRP
Now lets separate the functionality to follow SRP by creating 3 different classes. The DataRecords class where only data related functionality is implemented. The Logger class for logging purposes only and Email class for emailing functionality.
public class Logger : ILogger
{
public Logger()
{
// Code for Log initialization
}
public void Info(string info)
{
// Code for info information into the ErrorLog text file
}
public void Debug(string info)
{
// Code for Debug information into the ErrorLog text file
}
public void Error(string message, Exception ex = null)
{
// Code for Error information into the ErrorLog text file
}
public void Critical(string message, Exception ex = null)
{
// Code for Critical information into the ErrorLog text file
}
}
public class MailSender
{
public string EMailFrom { get; set; }
public string EMailTo { get; set; }
public string EMailSubject { get; set; }
public string EMailBody { get; set; }
public void SendEmail()
{
// Code for sending the mail
}
}
public class DataRecords
{
public long StrData { get; set; }
public DateTime Dt { get; set; }
private ILogger fileLogger;
private MailSender emailSender;
public DataRecords()
{
fileLogger = new Logger();
emailSender = new MailSender()
{
MailFrom = "emailfrom@xyz.com";
EMailTo = "emailto@xyz.com";
};
}
public void AddData()
{
try
{
fileLogger.Info("Add method Start");
// Code to add Data
// Once the Data has been added, then send the mail
emailSender.EMailSubject = "Data Added";
emailSender.EMailBody = "Data added to DataRecords";
emailSender.SendEmail();
}
catch (Exception ex)
{
fileLogger.Error("Error Occurred while Adding Data", ex.Message);
}
}
public void DeleteData()
{
try
{
// Code to delete Data
// Once the Data has been deleted, then send the mail
emailSender.EMailSubject = "Data Deleted";
emailSender.EMailBody = "Data Deleted from DataRecords";
emailSender.SendEmail();
fileLogger.Info("Delete Data Start at @" + DateTime.Now);
}
catch (Exception ex)
{
fileLogger.Error("Error Occurred while Deleting Data", ex);
}
}
}
What does SRP gives us ?
- Code being broken apart with each class having single responsibility thus making the code less complex, easier to maintain, read and modify.
- Unit testing this code is easier
- Increased code re-usability by splitting the code into succinct blocks that other classes can use
Potential Downside
- The "One Reason to Change" is a bit subjective and can be interpreted differently. The rules are not clear where to draw a line.
- Implementing SRP leads to compact small classes with thin methods which looks great from the surface but causes organizational risk. if these classes are not grouped well then it could actually increase the amount of work to make a change in the system which is opposite of what we wanted to achieve.