Summary: in this tutorial, you’ll learn how to use the C# Single Responsibility Principle to develop more maintainable and scalable software applications.
Introduction to the C# single responsibility principle
The Single Responsibility Principle (SRP) is the first principle in the five SOLID principles:
- Single Responsibility Principle (SRP)
- Open-Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
The Single Responsibility Principle states that a software module should have a single responsibility, i.e., it should have one and only one reason to change.
Having a single responsibility makes a class more cohesive and easier to maintain. It also makes the application more flexible, as changes to one responsibility do not impact other responsibilities.
For example, a class responsible for representing data should only be responsible for holding the data. It should not be responsible for saving the data to a database or performing unrelated tasks.
Separating these responsibilities into separate classes makes the application more maintainable and straightforward.
C# Single Responsibility Principle example
Suppose you need to create a program to manage invoices:
public class Invoice
{
public int InvoiceNo { get; set; }
public DateOnly IssuedDate { get; set; }
public string? Customer { get; set; }
public decimal Amount { get; set; }
public string? Description { get; set;}
public void Save()
{
Console.WriteLine($"Saved the invoice #{InvoiceNo}");
}
}
class Program
{
public static void Main(string[] args)
{
// create a new invoice
var invoice = new Invoice
{
InvoiceNo = 1,
Customer = "John Doe",
IssuedDate = new DateOnly(2023, 4, 1),
Description = "Website Design",
Amount = 1000
};
invoice.Save();
}
}
Code language: C# (cs)
How it works.
First, define an Invoice
class that has properties InvoiceNo
, Customer
, IssuedDate
, Description
, and Amount
. The Invoice
class also has the Save()
method that saves an invoice into a database:
public class Invoice
{
public int InvoiceNo { get; set; }
public DateOnly IssuedDate { get; set; }
public string? Customer { get; set; }
public decimal Amount { get; set; }
public string? Description { get; set;}
public void Save()
{
Console.WriteLine($"Saved the invoice #{InvoiceNo}");
}
}
Code language: C# (cs)
The Save()
method displays a message in the console instead of saving an invoice to a real database for demonstration purposes.
Second, create an invoice in the Main()
method of the Program
class and call the Save()
method to save the invoice:
class Program
{
public static void Main(string[] args)
{
// create a new invoice
var invoice = new Invoice
{
InvoiceNo = 1,
Customer = "John Doe",
IssuedDate = new DateOnly(2023, 4, 1),
Description = "Website Design",
Amount = 1000
};
invoice.Save();
}
}
Code language: C# (cs)
Initially, the Invoice
class looks fine. But when the program grows, you’ll realize that the Invoice
class has multiple responsibilities.
The Invoice
class represents the invoice data and is responsible for saving the invoice into storage. This violates the single responsibility principle, as the Invoice
class should have a single responsibility.
Let’s refactor the Invoice class to follow the single responsibility principle.
Refactoring the Invoice class to follow the single responsibility principle
First, create a separate class called InvoiceRepository
for saving an invoice and moving the Save()
method from the Invoice
class to the InvoiceRepository
class:
class InvoiceRepository
{
public void Save(Invoice invoice)
{
Console.WriteLine($"Saved the invoice #{invoice.InvoiceNo}");
}
}
Code language: C# (cs)
The Invoice
class now has a single responsibility representing invoice data only. The InvoiceRepository
is solely responsible for saving the invoice.
public class Invoice
{
public int InvoiceNo { get;set; }
public DateOnly IssuedDate { get; set; }
public string? Customer { get; set;}
public decimal Amount { get; set; }
public string? Description { get; set;}
}
Code language: C# (cs)
Second, create an invoice and save it to a database by calling the Save()
of the InvoiceRepository
class:
class Program
{
public static void Main(string[] args)
{
// create a new invoice
var invoice = new Invoice
{
InvoiceNo = 1,
Customer = "John Doe",
IssuedDate = new DateOnly(2023, 4, 1),
Description = "Website Design",
Amount = 1000
};
// save the invoice to a storage
var invoiceRepository = new InvoiceRepository();
invoiceRepository.Save(invoice);
}
}
Code language: C# (cs)
Put it all together.
public class Invoice
{
public int InvoiceNo { get;set; }
public DateOnly IssuedDate { get; set; }
public string? Customer { get; set;}
public decimal Amount { get; set; }
public string? Description { get; set;}
}
class InvoiceRepository
{
public void Save(Invoice invoice)
{
Console.WriteLine($"Saved the invoice #{invoice.InvoiceNo}");
}
}
class Program
{
public static void Main(string[] args)
{
// create a new invoice
var invoice = new Invoice
{
InvoiceNo = 1,
Customer = "John Doe",
IssuedDate = new DateOnly(2023, 4, 1),
Description = "Website Design",
Amount = 1000
};
// save the invoice to a storage
var invoiceRepository = new InvoiceRepository();
invoiceRepository.Save(invoice);
}
}
Code language: C# (cs)
Summary
- The Single Responsibility Principle states that a module should have only one reason to change.