๐ง Mastering 5 Design Patterns in C# - Part 2


โจ Introduction
Continuing our exploration of design patterns in C#, this article presents 5 additional patterns using the same notification system context. These patterns help solve different architectural challenges and improve code flexibility, scalability, and maintainability.
The patterns covered here are:
Adapter
Command
Proxy
Composite
Template Method
๐ 1. Adapter โ Bridging Interfaces
โ Intent
Convert the interface of a class into another interface clients expect.
๐ฏ Purpose
Use when you need to integrate incompatible interfaces without modifying existing code โ especially useful when working with third-party libraries or legacy systems.
๐งฉ Code
public class LegacyNotifier
{
public void LegacySend(string msg)
{
Console.WriteLine($"Legacy system sent: {msg}");
}
}
public interface INotifier
{
void Send(string message);
}
public class NotifierAdapter : INotifier
{
private readonly LegacyNotifier _legacy;
public NotifierAdapter(LegacyNotifier legacy)
{
_legacy = legacy;
}
public void Send(string message)
{
_legacy.LegacySend(message);
}
}
๐ง Explanation
LegacyNotifier
โ Old system with incompatible method.INotifier
โ Expected interface.NotifierAdapter
โ Bridges the gap between old and new.
๐ Pros
Promotes reuse of legacy code.
Decouples systems.
๐ Cons
Adds extra layer of abstraction.
Can become messy with many adapters.
๐งพ 2. Command โ Encapsulating Actions
โ Intent
Encapsulate a request as an object, allowing parameterization and queuing of requests.
๐ฏ Purpose
Use when you need to queue, log, or undo operations, or when actions should be executed later or remotely.
๐งฉ Code
public interface ICommand
{
void Execute();
}
public class EmailCommand : ICommand
{
private readonly string _message;
public EmailCommand(string message)
{
_message = message;
}
public void Execute()
{
Console.WriteLine($"Email command executed: {_message}");
}
}
public class CommandInvoker
{
private readonly List<ICommand> _commands = new();
public void AddCommand(ICommand command) => _commands.Add(command);
public void Run()
{
foreach (var cmd in _commands)
cmd.Execute();
}
}
๐ง Explanation
ICommand
โ Interface for commands.EmailCommand
โ Encapsulates email sending.CommandInvoker
โ Stores and executes commands.
๐ Pros
Enables undo/redo.
Decouples sender from receiver.
๐ Cons
Requires many small classes.
Can be overkill for simple tasks.
๐งฟ 3. Proxy โ Controlled Access
โ Intent
Provide a surrogate or placeholder for another object to control access to it.
๐ฏ Purpose
Use when you need to add access control, lazy loading, or logging without changing the original object.
๐งฉ Code
public interface INotifier
{
void Send(string message);
}
public class RealNotifier : INotifier
{
public void Send(string message)
{
Console.WriteLine($"Real notifier: {message}");
}
}
public class NotifierProxy : INotifier
{
private readonly RealNotifier _realNotifier = new();
public void Send(string message)
{
Console.WriteLine("Proxy: Checking permissions...");
_realNotifier.Send(message);
}
}
๐ง Explanation
RealNotifier
โ Actual implementation.NotifierProxy
โ Adds control before delegating.
๐ Pros
Adds security, logging, or caching.
Transparent to client.
๐ Cons
Adds complexity.
Can introduce performance overhead.
๐ณ 4. Composite โ Hierarchical Structures
โ Intent
Compose objects into tree structures to represent part-whole hierarchies.
๐ฏ Purpose
Use when you need to treat individual objects and groups uniformly, such as nested notifications or UI components.
๐งฉ Code
public interface INotifier
{
void Send(string message);
}
public class SingleNotifier : INotifier
{
public void Send(string message)
{
Console.WriteLine($"Single: {message}");
}
}
public class CompositeNotifier : INotifier
{
private readonly List<INotifier> _notifiers = new();
public void Add(INotifier notifier) => _notifiers.Add(notifier);
public void Send(string message)
{
foreach (var notifier in _notifiers)
notifier.Send(message);
}
}
๐ง Explanation
SingleNotifier
โ Individual notification.CompositeNotifier
โ Group of notifiers treated as one.
๐ Pros
Simplifies hierarchical structures.
Uniform treatment of objects.
๐ Cons
Can be hard to manage deep trees.
Debugging can be tricky.
๐งฌ 5. Template Method โ Algorithm Skeleton
โ Intent
Define the skeleton of an algorithm, deferring steps to subclasses.
๐ฏ Purpose
Use when you have a fixed process with customizable steps, like sending notifications with pre- and post-processing.
๐งฉ Code
public abstract class NotificationTemplate
{
public void Send(string message)
{
PreSend();
DoSend(message);
PostSend();
}
protected virtual void PreSend() => Console.WriteLine("Preparing to send...");
protected abstract void DoSend(string message);
protected virtual void PostSend() => Console.WriteLine("Finished sending.");
}
public class EmailNotification : NotificationTemplate
{
protected override void DoSend(string message)
{
Console.WriteLine($"Email sent: {message}");
}
}
๐ง Explanation
NotificationTemplate
โ Defines the algorithm structure.EmailNotification
โ Implements the core step.
๐ Pros
Promotes code reuse.
Enforces consistent process.
๐ Cons
Inflexible if steps vary too much.
Can lead to deep inheritance chains.
๐งพ Conclusion
These five additional design patterns offer powerful tools for structuring your C# applications:
Adapter bridges incompatible interfaces.
Command encapsulates actions for flexibility.
Proxy controls access and adds behavior.
Composite handles hierarchical structures.
Template Method enforces consistent workflows.
Using the same notification example across all patterns helps highlight their unique strengths and trade-offs
#CSharp #DesignPatterns #DotNet #SoftwareEngineering #CleanArchitecture #BackendDev #CodeQuality #ProgrammingTips #Adapter #Command #Proxy #Composite #TemplateMethod #
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.