Part 2 :Design Principles in Software Development
Tags: architecture, dotnet
Published: 2024-07-03 22:35:26

In software development, adhering to design principles is crucial for creating robust, maintainable, and scalable applications. Here, we'll explore five fundamental design principles: Dependency Inversion, Separation of Concerns, Single Responsibility, DRY (Don't Repeat Yourself), and Persistence Ignorance. Each principle helps to ensure that your codebase remains clean and efficient.

#### 1. Dependency Inversion Principle (DIP)
The Dependency Inversion Principle is the last of the SOLID principles. It states that high-level modules should not depend on low-level modules; both should depend on abstractions. Additionally, abstractions should not depend on details. Details should depend on abstractions.

**Example:**
```csharp
public interface IMessageService
{
    void SendMessage(string message);
}

public class EmailService : IMessageService
{
    public void SendMessage(string message)
    {
        // Send email
    }
}

public class Notification
{
    private readonly IMessageService _messageService;

    public Notification(IMessageService messageService)
    {
        _messageService = messageService;
    }

    public void Notify(string message)
    {
        _messageService.SendMessage(message);
    }
}
```
In this example, the `Notification` class depends on the abstraction `IMessageService` rather than a concrete implementation like `EmailService`. This allows for easy swapping of different message services without modifying the `Notification` class.

#### 2. Separation of Concerns (SoC)
Separation of Concerns involves organizing code into distinct sections, each responsible for a specific functionality. This principle helps to reduce code complexity and improve maintainability.

**Example:**
In a typical web application, you might separate concerns into layers such as:
- Presentation Layer: Handles the UI and user interaction.
- Business Logic Layer: Contains the core application logic.
- Data Access Layer: Manages data retrieval and storage.

```csharp
// Presentation Layer
public class UserController
{
    private readonly IUserService _userService;

    public UserController(IUserService userService)
    {
        _userService = userService;
    }

    public IActionResult GetUser(int id)
    {
        var user = _userService.GetUserById(id);
        return Ok(user);
    }
}

// Business Logic Layer
public interface IUserService
{
    User GetUserById(int id);
}

public class UserService : IUserService
{
    private readonly IUserRepository _userRepository;

    public UserService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public User GetUserById(int id)
    {
        return _userRepository.GetById(id);
    }
}

// Data Access Layer
public interface IUserRepository
{
    User GetById(int id);
}

public class UserRepository : IUserRepository
{
    public User GetById(int id)
    {
        // Retrieve user from database
    }
}
```
Each layer has a specific responsibility, making the codebase easier to manage and extend.

#### 3. Single Responsibility Principle (SRP)
The Single Responsibility Principle asserts that a class should have only one reason to change, meaning it should have only one job or responsibility.

**Example:**
```csharp
public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
}

public class UserRepository
{
    public void Save(User user)
    {
        // Save user to database
    }
}

public class UserValidator
{
    public bool Validate(User user)
    {
        // Validate user data
    }
}
```
Here, `User`, `UserRepository`, and `UserValidator` each have a single responsibility, adhering to the SRP.

#### 4. DRY (Don't Repeat Yourself)
The DRY principle emphasizes reducing the repetition of code by abstracting out common functionality into reusable components.

**Example:**
```csharp
public class EmailService
{
    public void SendEmail(string to, string subject, string body)
    {
        // Send email
    }
}

public class NotificationService
{
    private readonly EmailService _emailService;

    public NotificationService(EmailService emailService)
    {
        _emailService = emailService;
    }

    public void NotifyUser(string email, string message)
    {
        _emailService.SendEmail(email, "Notification", message);
    }
}
```
By using the `EmailService` in `NotificationService`, we avoid duplicating email sending logic.

#### 5. Persistence Ignorance
Persistence Ignorance means that the business logic of an application should not be aware of how data is persisted. This principle ensures that the core logic remains independent of the data access technology used.

**Example:**
```csharp
public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public interface IProductRepository
{
    void Save(Product product);
}

public class ProductService
{
    private readonly IProductRepository _productRepository;

    public ProductService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }

    public void AddProduct(Product product)
    {
        _productRepository.Save(product);
    }
}
```
The `ProductService` class does not know or care about how the `ProductRepository` class persists data, adhering to the principle of persistence ignorance.

### Conclusion
Understanding and applying these design principles can significantly enhance the quality of your software. By following Dependency Inversion, Separation of Concerns, Single Responsibility, DRY, and Persistence Ignorance, you can create applications that are easier to understand, maintain, and extend.