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


โจ Introduction
Continuing our deep dive into design patterns, this third part explores 5 additional patterns that enhance scalability, modularity, and dependency control. Still using the notification system as our context, weโll cover:
Flyweight
Prototype
Visitor
Interpreter
Service Locator
๐ชถ 1. Flyweight โ Memory Optimization
โ Intent
Minimize memory usage by sharing common data between similar objects.
๐ฏ Purpose
Useful when your system creates many similar objects, like notifications with repeated styles.
๐งฉ Code
public class NotificationStyle
{
public string Font { get; set; }
public string Color { get; set; }
}
public class StyleFactory
{
private readonly Dictionary<string, NotificationStyle> _styles = new();
public NotificationStyle GetStyle(string key)
{
if (!_styles.ContainsKey(key))
{
_styles[key] = new NotificationStyle { Font = "Arial", Color = key };
}
return _styles[key];
}
}
๐ง Explanation
StyleFactory
shares style instances to save memory.
๐ Pros
Reduces memory footprint.
Improves performance.
๐ Cons
Adds complexity.
Harder with mutable objects.
๐งฌ 2. Prototype โ Object Cloning
โ Intent
Create new objects by copying an existing instance.
๐ฏ Purpose
Ideal when object creation is expensive or complex, and you want to duplicate with slight variations.
๐งฉ Code
public class Notification : ICloneable
{
public string Title { get; set; }
public string Message { get; set; }
public object Clone()
{
return MemberwiseClone();
}
}
๐ง Explanation
ICloneable
allows quick duplication of notifications.
๐ Pros
Fast object creation.
Avoids repetitive setup.
๐ Cons
May result in shallow copies.
Tricky with deep object graphs.
๐งญ 3. Visitor โ External Operations
โ Intent
Add operations to objects without modifying their classes.
๐ฏ Purpose
Great for performing multiple actions on a structure of objects, like exporting notifications.
๐งฉ Code
public interface IVisitable
{
void Accept(INotificationVisitor visitor);
}
public interface INotificationVisitor
{
void Visit(Notification notification);
}
public class Notification : IVisitable
{
public string Message { get; set; }
public void Accept(INotificationVisitor visitor)
{
visitor.Visit(this);
}
}
public class ExportVisitor : INotificationVisitor
{
public void Visit(Notification notification)
{
Console.WriteLine($"Exporting: {notification.Message}");
}
}
๐ง Explanation
Visitor
separates export logic from the core class.
๐ Pros
Highly extensible.
Keeps classes clean.
๐ Cons
Adds complexity.
Harder with deep hierarchies.
๐งฎ 4. Interpreter โ Custom Language
โ Intent
Interpret sentences in a custom language.
๐ฏ Purpose
Useful for custom notification commands, like filters or rules.
๐งฉ Code
public interface IExpression
{
bool Interpret(string context);
}
public class ContainsExpression : IExpression
{
private readonly string _keyword;
public ContainsExpression(string keyword)
{
_keyword = keyword;
}
public bool Interpret(string context)
{
return context.Contains(_keyword);
}
}
๐ง Explanation
ContainsExpression
checks if a message contains a keyword.
๐ Pros
Flexible for rule engines.
Easy to extend.
๐ Cons
Can be slow.
Hard to scale for complex grammars.
๐งญ 5. Service Locator โ Centralized Access
โ Intent
Provide a way to locate services without direct dependencies.
๐ฏ Purpose
Ideal for managing dependencies like notification channels.
๐งฉ Code
public interface INotificationService
{
void Send(string message);
}
public class EmailService : INotificationService
{
public void Send(string message) => Console.WriteLine($"Email: {message}");
}
public class ServiceLocator
{
private static readonly Dictionary<string, INotificationService> _services = new();
public static void Register(string key, INotificationService service)
{
_services[key] = service;
}
public static INotificationService GetService(string key)
{
return _services[key];
}
}
๐ง Explanation
ServiceLocator
centralizes access to notification services.
๐ Pros
Reduces coupling.
Easy to swap implementations.
๐ Cons
Can obscure dependencies.
Less transparent than dependency injection.
๐งพ Conclusion
These five patterns expand your toolkit for building scalable, maintainable systems:
Flyweight saves memory.
Prototype enables cloning.
Visitor separates operations.
Interpreter supports custom rules.
Service Locator centralizes dependencies.
#CSharp #DesignPatterns #DotNet #SoftwareEngineering #CleanArchitecture #BackendDev #CodeQuality #ProgrammingTips #Flyweight #Protoype #Visitor #Interpreter #ServiceLocator
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.