π§ 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
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.