In the dynamic realm of software development, the need for scalable, maintainable, and efficient code is paramount. One key design principle that addresses these concerns is Separation of Concerns (SoC). In this blog post, we’ll delve into a real-world example using C# to illustrate how SoC can transform a web application managing a list of books.
Understanding Separation of Concerns
SoC advocates breaking down a software system into distinct sections, each catering to a specific concern. By compartmentalizing responsibilities, SoC aims to simplify development, enhance maintainability, and encourage code reuse.
The Initial Scenario
Consider a basic web application that tracks a collection of books. In a scenario without SoC, you might encounter a monolithic class handling everything – data access, business logic, and presentation.
public class BookController
{
public ActionResult DisplayBooks()
{
// Fetch books from the database
List<Book> books = DatabaseAccess.GetBooks();
// Apply business logic (e.g., filter out expired books)
List<Book> filteredBooks = BusinessLogic.FilterExpiredBooks(books);
// Render the view
return View(filteredBooks);
}
public ActionResult AddBook(Book newBook)
{
// Validate input
if (!InputValidator.ValidateBook(newBook))
{
// Handle validation error
return View("ValidationError");
}
// Add the book to the database
DatabaseAccess.AddBook(newBook);
// Redirect to the display page
return RedirectToAction("DisplayBooks");
}
}
Embracing Separation of Concerns
Now, let’s embark on a journey to refactor this code, adhering to the Separation of Concerns principle.
1. Data Access Layer
We begin by creating a dedicated class for data access:
public class BookRepository
{
public List<Book> GetBooks()
{
// Code to fetch books from the database
}
public void AddBook(Book newBook)
{
// Code to add a book to the database
}
}
2. Business Logic Layer
Next, we encapsulate business logic in a separate class:
public class BookService
{
private readonly BookRepository _bookRepository;
public BookService(BookRepository bookRepository)
{
_bookRepository = bookRepository;
}
public List<Book> GetFilteredBooks()
{
List<Book> books = _bookRepository.GetBooks();
// Apply business logic (e.g., filter out expired books)
return BusinessLogic.FilterExpiredBooks(books);
}
public void AddNewBook(Book newBook)
{
// Validate input
if (!InputValidator.ValidateBook(newBook))
{
// Handle validation error
throw new ValidationException("Invalid book data");
}
_bookRepository.AddBook(newBook);
}
}
3. Presentation Layer
Finally, we focus on the presentation layer:
public class BookController
{
private readonly BookService _bookService;
public BookController(BookService bookService)
{
_bookService = bookService;
}
public ActionResult DisplayBooks()
{
List<Book> filteredBooks = _bookService.GetFilteredBooks();
return View(filteredBooks);
}
public ActionResult AddBook(Book newBook)
{
try
{
_bookService.AddNewBook(newBook);
return RedirectToAction("DisplayBooks");
}
catch (ValidationException ex)
{
// Handle validation error
return View("ValidationError");
}
}
}
Embracing the Benefits
In this refactored example:
- BookRepository handles data access.
- BookService encapsulates business logic.
- BookController focuses on user interaction and presentation.
By separating concerns, the code becomes more modular, easier to test, and promotes maintainability. Each class has a distinct responsibility, making it easier to comprehend, modify, and extend the application.