Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Tim Corey
35m 50s
Design patterns are reusable solutions to common software development problems, providing templates to structure and implement object-oriented code in a more efficient and maintainable way. They help developers solve problems with object creation, structure, and communication in a flexible and scalable manner. Design patterns serve as best practice concepts that guide developers in writing better code. One of the foundational principles in software design is the Single Responsibility Principle (SRP), which is part of the SOLID principles.
In his video, "Design Patterns: Single Responsibility Principle Explained Practically in C# (The S in SOLID)," Tim Corey explores the Single Responsibility Principle (SRP), highlighting its significance in software design and providing practical insights on how to implement it effectively. This article offers a concise overview of the key takeaways from his video, emphasizing the importance of SRP in creating clean, maintainable code.
In software design, SOLID principles are crucial for creating maintainable and scalable code. They ensure that code is easy to understand, test, and modify. The five principles - Single Responsibility Principle (SRP), Open/Closed Principle (OCP), Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP), and Dependency Inversion Principle (DIP) - are integral to object-oriented design and can be applied within design patterns to make solutions more robust.
By applying design patterns in C#, developers can solve common problems more effectively. Whether it's creating objects, defining tree structures, or ensuring reusability with single instances, design patterns provide predefined solutions that enhance software architecture. Patterns like the Factory Method, Builder, and Singleton provide flexible, reusable solutions, while behavioral and structural patterns help manage complexity and improve communication within systems. By learning and utilizing these patterns, developers can build systems that are easier to maintain and extend.
Tim discusses the concept of SRP, emphasizing that it is crucial for developers to ensure their code adheres to best practices. SRP states that a class should have only one responsibility or reason to change. This principle helps maintain clean, maintainable, and scalable code.
Tim sets up a simple console application in C# that asks for the user's first and last name, validates these names, and then generates a username. The initial implementation violates SRP, providing an excellent opportunity to demonstrate how to refactor code to adhere to this principle.
Tim explains SRP by highlighting the multiple responsibilities within the initial class:
Each of these responsibilities represents a different reason for the class to change, violating SRP.
Tim demonstrates how to refactor the code to follow SRP by extracting each responsibility into its own class. This approach ensures that each class has a single reason to change, making the code more modular and easier to maintain.
Tim provides a practical example of refactoring:
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Welcome to my application");
Console.Write("Enter your first name: ");
string firstName = Console.ReadLine();
Console.Write("Enter your last name: ");
string lastName = Console.ReadLine();
if (string.IsNullOrWhiteSpace(firstName) || string.IsNullOrWhiteSpace(lastName))
{
Console.WriteLine("You did not give us valid information!");
Console.ReadLine();
return;
}
var userName = $"{firstName.Substring(0, 1)}{lastName}".ToLower();
Console.WriteLine($"Your username is {userName}");
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
}Imports System
Module Program
Sub Main(args As String())
Console.WriteLine("Welcome to my application")
Console.Write("Enter your first name: ")
Dim firstName As String = Console.ReadLine()
Console.Write("Enter your last name: ")
Dim lastName As String = Console.ReadLine()
If String.IsNullOrWhiteSpace(firstName) OrElse String.IsNullOrWhiteSpace(lastName) Then
Console.WriteLine("You did not give us valid information!")
Console.ReadLine()
Return
End If
Dim userName = $"{firstName.Substring(0, 1)}{lastName}".ToLower()
Console.WriteLine($"Your username is {userName}")
Console.WriteLine("Press enter to close...")
Console.ReadLine()
End Sub
End ModuleFirst, Tim creates a class to handle standard messages shown to the user. This class will manage welcome messages and end messages.
public class StandardMessages
{
public static void WelcomeMessage()
{
Console.WriteLine("Welcome to my application");
}
public static void EndApplication()
{
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
public static void ShowValidationErrorMessage()
{
Console.WriteLine("You did not give us valid information!");
}
}Public Class StandardMessages
Public Shared Sub WelcomeMessage()
Console.WriteLine("Welcome to my application")
End Sub
Public Shared Sub EndApplication()
Console.WriteLine("Press enter to close...")
Console.ReadLine()
End Sub
Public Shared Sub ShowValidationErrorMessage()
Console.WriteLine("You did not give us valid information!")
End Sub
End ClassIn the Program class, replace the direct calls to Console.WriteLine and Console.ReadLine with calls to the methods in the StandardMessages class:
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
// Other code...
StandardMessages.EndApplication();
}
}Module Program
Sub Main(args As String())
StandardMessages.WelcomeMessage()
' Other code...
StandardMessages.EndApplication()
End Sub
End ModuleNext, Tim creates a class to handle capturing the person's first and last name. This class will be responsible for collecting user input and returning a Person object.
public class PersonDataCapture
{
public static Person Capture()
{
Person output = new Person();
Console.Write("Enter your first name: ");
output.FirstName = Console.ReadLine();
Console.Write("Enter your last name: ");
output.LastName = Console.ReadLine();
return output;
}
}Public Class PersonDataCapture
Public Shared Function Capture() As Person
Dim output As New Person()
Console.Write("Enter your first name: ")
output.FirstName = Console.ReadLine()
Console.Write("Enter your last name: ")
output.LastName = Console.ReadLine()
Return output
End Function
End ClassYou also need a Person class to hold the first and last name of the user.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}Public Class Person
Public Property FirstName As String
Public Property LastName As String
End ClassIn the Program class, replace the direct user input handling with a call to PersonDataCapture.Capture:
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
// Other code...
StandardMessages.EndApplication();
}
}Module Program
Sub Main(args As String())
StandardMessages.WelcomeMessage()
Dim user As Person = PersonDataCapture.Capture()
' Other code...
StandardMessages.EndApplication()
End Sub
End ModuleNext, Tim creates a class to handle validation of the person's first and last name. This class will be responsible for ensuring the names are not null or whitespace.
public class PersonValidator
{
public static bool Validate(Person person)
{
if (string.IsNullOrWhiteSpace(person.FirstName))
{
StandardMessages.ShowValidationErrorMessage("first name");
return false;
}
if (string.IsNullOrWhiteSpace(person.LastName))
{
StandardMessages.ShowValidationErrorMessage("last name");
return false;
}
return true;
}
}Public Class PersonValidator
Public Shared Function Validate(person As Person) As Boolean
If String.IsNullOrWhiteSpace(person.FirstName) Then
StandardMessages.ShowValidationErrorMessage("first name")
Return False
End If
If String.IsNullOrWhiteSpace(person.LastName) Then
StandardMessages.ShowValidationErrorMessage("last name")
Return False
End If
Return True
End Function
End ClassIn the Program class, replace the validation code with a call to PersonValidator.Validate:
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
if (!PersonValidator.Validate(user))
{
StandardMessages.EndApplication();
return;
}
// Other code...
StandardMessages.EndApplication();
}
}Module Program
Sub Main(args As String())
StandardMessages.WelcomeMessage()
Dim user As Person = PersonDataCapture.Capture()
If Not PersonValidator.Validate(user) Then
StandardMessages.EndApplication()
Return
End If
' Other code...
StandardMessages.EndApplication()
End Sub
End ModuleTim moves the username generation and account creation logic into a new AccountGenerator class.
Creating the AccountGenerator Class:
public class AccountGenerator
{
public static void CreateAccount(Person user)
{
string username = $"{user.FirstName.Substring(0, 1)}{user.LastName}".ToLower();
Console.WriteLine($"Your username is: {username}");
}
}Public Class AccountGenerator
Public Shared Sub CreateAccount(user As Person)
Dim username As String = $"{user.FirstName.Substring(0, 1)}{user.LastName}".ToLower()
Console.WriteLine($"Your username is: {username}")
End Sub
End ClassUpdating the Main Class:
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
bool isUserValid = PersonValidator.Validate(user);
if (!isUserValid)
{
StandardMessages.EndApplication();
return;
}
AccountGenerator.CreateAccount(user);
StandardMessages.EndApplication();
}
}Module Program
Sub Main(args As String())
StandardMessages.WelcomeMessage()
Dim user As Person = PersonDataCapture.Capture()
Dim isUserValid As Boolean = PersonValidator.Validate(user)
If Not isUserValid Then
StandardMessages.EndApplication()
Return
End If
AccountGenerator.CreateAccount(user)
StandardMessages.EndApplication()
End Sub
End Module
In this concluding section, Tim Corey summarizes the benefits and implementation of the Single Responsibility Principle (SRP) through the refactoring process of the demo code. He highlights the advantages of breaking the application into smaller, focused classes.
Simplified Code Maintenance:
Improved Readability:
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
bool isUserValid = PersonValidator.Validate(user);
if (!isUserValid)
{
StandardMessages.EndApplication();
return;
}
AccountGenerator.CreateAccount(user);
StandardMessages.EndApplication();StandardMessages.WelcomeMessage()
Dim user As Person = PersonDataCapture.Capture()
Dim isUserValid As Boolean = PersonValidator.Validate(user)
If Not isUserValid Then
StandardMessages.EndApplication()
Return
End If
AccountGenerator.CreateAccount(user)
StandardMessages.EndApplication()Reduced Complexity:
public class StandardMessages
{
public static void WelcomeMessage()
{
Console.WriteLine("Welcome to my application");
}
public static void EndApplication()
{
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
public static void ShowValidationErrorMessage(string fieldName)
{
Console.WriteLine($"You did not give us a valid {fieldName}!");
}
}Public Class StandardMessages
Public Shared Sub WelcomeMessage()
Console.WriteLine("Welcome to my application")
End Sub
Public Shared Sub EndApplication()
Console.WriteLine("Press enter to close...")
Console.ReadLine()
End Sub
Public Shared Sub ShowValidationErrorMessage(fieldName As String)
Console.WriteLine($"You did not give us a valid {fieldName}!")
End Sub
End Class
Ease of Code Changes:
Better Debugging and Collaboration:
Tim addresses a common concern that applying SRP results in too many classes, making the project cumbersome:
Navigation and Understanding:
Performance and Storage:
Balance and Excess:
Tim encourages developers to apply SRP gradually, especially in existing codebases. Start with small changes and new code to align with SRP principles. This incremental approach ensures smoother transitions and continual improvement.
Tim Corey's refactoring example demonstrates how adhering to the Single Responsibility Principle (SRP) results in cleaner, more maintainable code. By breaking down responsibilities into smaller, focused classes, developers can improve readability, debugging, and collaboration within their codebases. This foundational principle of the SOLID design patterns paves the way for more advanced principles and best practices in software development.
For more detailed information and code samples, please watch his video and visit his channel for more design patterns videos.
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy