DEV Community

mohamed Tayel
mohamed Tayel

Posted on

Part 2 :Design Principles in Software Development

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:

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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.
// 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
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (3)

Collapse
 
arifrafik profile image
arifrafik

Excellent.
Thanks

Collapse
 
syedmuhammadaliraza profile image
Syed Muhammad Ali Raza

very well explained

Collapse
 
lincpa profile image
Lin Pengcheng