Design Patterns in C# for Advanced: From Theory to Code
\n\nDesign patterns are not just abstract concepts from textbooks. For an experienced C# developer, they become a toolkit for creating flexible, extensible, and maintainable application architecture. In this article, we will cover three powerful patterns that often remain beyond basic courses: Visitor, Strategy, and Command. You will see not only their implementation in C# but also understand how they solve real-world problems in the .NET ecosystem.
\n\n1. Visitor Pattern — Working with Object Hierarchies
\n\nThe Visitor pattern allows you to add new operations to existing classes without modifying their structure. This is especially useful when you have a complex object hierarchy and want to perform different actions depending on their type.
\n\nProblem
\nImagine you have a document management system with classes TextDocument, Spreadsheet, and Presentation. You need to add export functionality to PDF, HTML, and XML. Without the Visitor pattern, you would have to modify each class, violating the Open/Closed Principle (OCP).
Implementation in C#
\n// 1. Element interface\npublic interface IElement\n{\n void Accept(IVisitor visitor);\n}\n\n// 2. Concrete elements\npublic class TextDocument : IElement\n{\n public string Content { get; set; }\n public void Accept(IVisitor visitor) => visitor.Visit(this);\n}\n\npublic class Spreadsheet : IElement\n{\n public List<List<string>> Rows { get; set; }\n public void Accept(IVisitor visitor) => visitor.Visit(this);\n}\n\n// 3. Visitor interface\npublic interface IVisitor\n{\n void Visit(TextDocument doc);\n void Visit(Spreadsheet sheet);\n}\n\n// 4. Concrete visitor — HTML export\npublic class HtmlExportVisitor : IVisitor\n{\n public void Visit(TextDocument doc)\n {\n Console.WriteLine($"<h1>{doc.Content}</h1>");\n }\n\n public void Visit(Spreadsheet sheet)\n {\n Console.WriteLine("<table>");\n foreach (var row in sheet.Rows)\n {\n Console.WriteLine("<tr><td>" + string.Join("</td><td>", row) + "</td></tr>");\n }\n Console.WriteLine("</table>");\n }\n}\n\n// Usage\nvar docs = new List<IElement> { new TextDocument { Content = "Hello" }, new Spreadsheet { Rows = new List<List<string>> { new List<string> { "A", "B" } } } };\nvar visitor = new HtmlExportVisitor();\nforeach (var doc in docs) doc.Accept(visitor);\n\nPros: easy to add new operations without touching existing classes. Cons: if the element hierarchy changes frequently, Visitor becomes inconvenient.
\n\n2. Strategy Pattern — Dynamic Algorithm Switching
\n\nThe Strategy pattern allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. This is a classic way to avoid huge if-else or switch constructs.
Problem
\nYou need to implement a shipping cost calculation system. Different shipping methods exist: courier, postal, and pickup. Each method has its own pricing logic.
\n\nImplementation in C#
\n// 1. Strategy interface\npublic interface IShippingStrategy\n{\n decimal CalculateCost(Order order);\n}\n\n// 2. Concrete strategies\npublic class CourierShipping : IShippingStrategy\n{\n public decimal CalculateCost(Order order) => order.Weight * 5.0m + 100;\n}\n\npublic class PostShipping : IShippingStrategy\n{\n public decimal CalculateCost(Order order) => order.Weight * 2.0m + 50;\n}\n\npublic class PickupShipping : IShippingStrategy\n{\n public decimal CalculateCost(Order