🧠 Mastering 5 Design Patterns in C# - Part 5

✨ Introduction

Continuing our comprehensive series on design patterns in C#, this fourth part introduces 5 additional patterns that enhance modularity, control flow, and system extensibility. Still using the notification system context, we’ll explore:

  • Dependency Injection

  • Null Object

  • Facade

  • Bridge

  • Data Mapper


🧩 1. Dependency Injection – Inversion of Control

βœ… Intent

Decouple object creation from its usage by injecting dependencies.

🎯 Purpose

Use when you want to promote loose coupling and testability, especially in systems with multiple interchangeable services.

🧩 Code

public interface INotificationService
{
    void Send(string message);
}

public class EmailService : INotificationService
{
    public void Send(string message) => Console.WriteLine($"Email: {message}");
}

public class NotificationSender
{
    private readonly INotificationService _service;

    public NotificationSender(INotificationService service)
    {
        _service = service;
    }

    public void Send(string message)
    {
        _service.Send(message);
    }
}

🧠 Explanation

  • NotificationSender receives its dependency via constructor injection.

πŸ‘ Pros

  • Highly testable.

  • Promotes loose coupling.

πŸ‘Ž Cons

  • Requires setup (e.g., DI container).

  • Can be verbose in small apps.


🚫 2. Null Object – Safe Default Behavior

βœ… Intent

Provide a default object that does nothing instead of using null.

🎯 Purpose

Use when you want to avoid null checks and ensure safe behavior when no service is available.

🧩 Code

public class NullNotificationService : INotificationService
{
    public void Send(string message)
    {
        Console.WriteLine("No notification sent.");
    }
}

🧠 Explanation

  • Implements the same interface but performs no action.

πŸ‘ Pros

  • Eliminates null checks.

  • Simplifies control flow.

πŸ‘Ž Cons

  • May hide errors.

  • Can lead to silent failures.


🎯 3. Facade – Simplified Interface

βœ… Intent

Provide a unified interface to a set of interfaces in a subsystem.

🎯 Purpose

Use when you want to simplify complex subsystems and expose a clean API to clients.

🧩 Code

public class NotificationFacade
{
    private readonly EmailService _email = new();
    private readonly SmsService _sms = new();

    public void SendAll(string message)
    {
        _email.Send(message);
        _sms.Send(message);
    }
}

🧠 Explanation

  • NotificationFacade wraps multiple services into one method.

πŸ‘ Pros

  • Simplifies usage.

  • Hides complexity.

πŸ‘Ž Cons

  • Can become bloated.

  • May reduce flexibility.


πŸŒ‰ 4. Bridge – Decoupling Abstraction from Implementation

βœ… Intent

Separate abstraction from implementation so they can vary independently.

🎯 Purpose

Use when you want to combine multiple dimensions of variation, like notification types and delivery methods.

🧩 Code

public interface INotificationSender
{
    void Send(string message);
}

public abstract class Notification
{
    protected INotificationSender _sender;

    protected Notification(INotificationSender sender)
    {
        _sender = sender;
    }

    public abstract void Notify(string message);
}

public class UrgentNotification : Notification
{
    public UrgentNotification(INotificationSender sender) : base(sender) { }

    public override void Notify(string message)
    {
        Console.WriteLine("Urgent:");
        _sender.Send(message);
    }
}

🧠 Explanation

  • Notification is the abstraction; INotificationSender is the implementation.

πŸ‘ Pros

  • Flexible combinations.

  • Reduces class explosion.

πŸ‘Ž Cons

  • Adds abstraction layers.

  • Can be harder to trace.


πŸ—ƒοΈ 5. Data Mapper – Separation of Data Access

βœ… Intent

Map between in-memory objects and database records.

🎯 Purpose

Use when you want to separate business logic from persistence logic, especially in layered architectures.

🧩 Code

public class Notification
{
    public int Id { get; set; }
    public string Message { get; set; }
}

public class NotificationMapper
{
    public Notification MapFromDbRow(Dictionary<string, object> row)
    {
        return new Notification
        {
            Id = (int)row["Id"],
            Message = (string)row["Message"]
        };
    }
}

🧠 Explanation

  • NotificationMapper converts raw data into domain objects.

πŸ‘ Pros

  • Clean separation of concerns.

  • Easier to test business logic.

πŸ‘Ž Cons

  • Requires manual mapping.

  • Can be repetitive without tools.


🧾 Conclusion

These five patterns further strengthen your architectural toolbox:

  • Dependency Injection promotes loose coupling.

  • Null Object ensures safe defaults.

  • Facade simplifies complex systems.

  • Bridge enables flexible abstractions.

  • Data Mapper separates data access from logic.

With 25 patterns now covered, you’re well-equipped to design clean, scalable, and maintainable C# applications.

#CSharp #DesignPatterns #DotNet #SoftwareArchitecture #DependencyInjection #CleanCode #BackendDev #ScalableSystems #Developer #Development #DependecyInjection #NullObjectPattern #FacadePattern #BridgePattern #DataMapperPattern

0
Subscribe to my newsletter

Read articles from Johnny Hideki Kinoshita de Faria directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Johnny Hideki Kinoshita de Faria
Johnny Hideki Kinoshita de Faria

Technology professional with over 15 years of experience delivering innovative, scalable, and secure solutions β€” especially within the financial sector. I bring deep expertise in Oracle PL/SQL (9+ years), designing robust data architectures that ensure performance and reliability. On the back-end side, I’ve spent 6 years building enterprise-grade applications using .NET, applying best practices like TDD and clean code to deliver high-quality solutions. In addition to my backend strengths, I have 6 years of experience with PHP and JavaScript, allowing me to develop full-stack web applications that combine strong performance with intuitive user interfaces. I've led and contributed to projects involving digital account management, integration of VISA credit and debit transactions, modernization of payment systems, financial analysis tools, and fraud prevention strategies. Academically, I hold a postgraduate certificate in .NET Architecture and an MBA in IT Project Management, blending technical skill with business acumen. Over the past 6 years, I’ve also taken on leadership roles β€” managing teams, mentoring developers, and driving strategic initiatives. I'm fluent in agile methodologies and make consistent use of tools like Azure Boards to coordinate tasks and align team performance with delivery goals.