15 Design Patterns C# Interview Questions and Answers
Prepare for your C# interview with this guide on design patterns. Enhance your understanding and improve your coding skills with expert insights.
Prepare for your C# interview with this guide on design patterns. Enhance your understanding and improve your coding skills with expert insights.
Design patterns in C# are essential tools for software developers, providing proven solutions to common design problems. These patterns help create more maintainable, scalable, and efficient code by offering a standard terminology and best practices. Understanding and implementing design patterns can significantly improve the quality of your software projects and streamline the development process.
This article offers a curated selection of interview questions focused on design patterns in C#. Reviewing these questions will help you deepen your understanding of key concepts and prepare you to articulate your knowledge effectively during technical interviews.
The Singleton pattern restricts a class to a single instance, useful for coordinating actions across a system. In C#, it typically involves a private constructor, a static variable for the instance, and a public static method for access.
public class Singleton { private static Singleton _instance; private static readonly object _lock = new object(); private Singleton() { } public static Singleton Instance { get { lock (_lock) { if (_instance == null) { _instance = new Singleton(); } return _instance; } } } }
The Factory Method pattern defines an interface for creating an object, allowing subclasses to alter the type of objects created. This is useful when the exact type is determined at runtime.
public interface IProduct { string Operation(); } public class ConcreteProductA : IProduct { public string Operation() { return "Result of ConcreteProductA"; } } public class ConcreteProductB : IProduct { public string Operation() { return "Result of ConcreteProductB"; } } public abstract class Creator { public abstract IProduct FactoryMethod(); public string SomeOperation() { var product = FactoryMethod(); return "Creator: The same creator's code has just worked with " + product.Operation(); } } public class ConcreteCreatorA : Creator { public override IProduct FactoryMethod() { return new ConcreteProductA(); } } public class ConcreteCreatorB : Creator { public override IProduct FactoryMethod() { return new ConcreteProductB(); } } class Client { public void Main() { Console.WriteLine("App: Launched with the ConcreteCreatorA."); ClientCode(new ConcreteCreatorA()); Console.WriteLine(""); Console.WriteLine("App: Launched with the ConcreteCreatorB."); ClientCode(new ConcreteCreatorB()); } public void ClientCode(Creator creator) { Console.WriteLine("Client: I'm not aware of the creator's class, but it still works.\n" + creator.SomeOperation()); } }
The Builder pattern involves a Builder interface, ConcreteBuilder, Director, and Product. The Builder defines steps to build the product, ConcreteBuilder implements these steps, Director controls the process, and Product is the final object.
public class Car { public string Engine { get; set; } public string Wheels { get; set; } public string Body { get; set; } } public interface ICarBuilder { void BuildEngine(); void BuildWheels(); void BuildBody(); Car GetCar(); } public class SportsCarBuilder : ICarBuilder { private Car _car = new Car(); public void BuildEngine() => _car.Engine = "V8 Engine"; public void BuildWheels() => _car.Wheels = "18 inch Alloy Wheels"; public void BuildBody() => _car.Body = "Sports Body"; public Car GetCar() => _car; } public class Director { private ICarBuilder _builder; public Director(ICarBuilder builder) => _builder = builder; public void Construct() { _builder.BuildEngine(); _builder.BuildWheels(); _builder.BuildBody(); } } class Program { static void Main() { ICarBuilder builder = new SportsCarBuilder(); Director director = new Director(builder); director.Construct(); Car car = builder.GetCar(); } }
The Prototype pattern involves creating an interface or abstract class for cloning objects. Concrete classes implement this interface to provide cloning logic.
public abstract class Prototype { public abstract Prototype Clone(); } public class ConcretePrototype : Prototype { public int Id { get; set; } public string Name { get; set; } public override Prototype Clone() { return (Prototype)this.MemberwiseClone(); } } ConcretePrototype original = new ConcretePrototype { Id = 1, Name = "Prototype" }; ConcretePrototype clone = (ConcretePrototype)original.Clone();
The Adapter pattern involves creating an adapter class that implements the target interface and wraps an instance of the adaptee class, allowing the client to interact with the adaptee through the target interface.
public interface ITarget { void Request(); } public class Adaptee { public void SpecificRequest() { Console.WriteLine("Called SpecificRequest()"); } } public class Adapter : ITarget { private readonly Adaptee _adaptee; public Adapter(Adaptee adaptee) { _adaptee = adaptee; } public void Request() { _adaptee.SpecificRequest(); } } class Program { static void Main() { Adaptee adaptee = new Adaptee(); ITarget target = new Adapter(adaptee); target.Request(); } }
The Composite pattern allows you to compose objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.
using System; using System.Collections.Generic; public abstract class Component { public string Name { get; set; } public Component(string name) => Name = name; public abstract void Display(int depth); } public class Leaf : Component { public Leaf(string name) : base(name) { } public override void Display(int depth) { Console.WriteLine(new String('-', depth) + Name); } } public class Composite : Component { private List<Component> _children = new List<Component>(); public Composite(string name) : base(name) { } public void Add(Component component) { _children.Add(component); } public void Remove(Component component) { _children.Remove(component); } public override void Display(int depth) { Console.WriteLine(new String('-', depth) + Name); foreach (Component component in _children) { component.Display(depth + 2); } } } public class Program { public static void Main(string[] args) { Composite root = new Composite("root"); root.Add(new Leaf("Leaf A")); root.Add(new Leaf("Leaf B")); Composite comp = new Composite("Composite X"); comp.Add(new Leaf("Leaf XA")); comp.Add(new Leaf("Leaf XB")); root.Add(comp); root.Add(new Leaf("Leaf C")); Leaf leaf = new Leaf("Leaf D"); root.Add(leaf); root.Remove(leaf); root.Display(1); } }
The Decorator pattern allows behavior to be added to individual objects without affecting others. It adheres to the Open/Closed Principle, allowing classes to be open for extension but closed for modification.
public interface IComponent { void Operation(); } public class ConcreteComponent : IComponent { public void Operation() { Console.WriteLine("ConcreteComponent Operation"); } } public abstract class Decorator : IComponent { protected IComponent _component; public Decorator(IComponent component) { _component = component; } public virtual void Operation() { _component.Operation(); } } public class ConcreteDecoratorA : Decorator { public ConcreteDecoratorA(IComponent component) : base(component) { } public override void Operation() { base.Operation(); AddedBehavior(); } void AddedBehavior() { Console.WriteLine("ConcreteDecoratorA Added Behavior"); } } IComponent component = new ConcreteComponent(); IComponent decoratedComponent = new ConcreteDecoratorA(component); decoratedComponent.Operation();
The Facade pattern provides a simplified interface to a complex subsystem, hiding its complexities and providing an easy-to-use interface for the client.
public class SubsystemA { public void OperationA() => Console.WriteLine("SubsystemA: OperationA"); } public class SubsystemB { public void OperationB() => Console.WriteLine("SubsystemB: OperationB"); } public class SubsystemC { public void OperationC() => Console.WriteLine("SubsystemC: OperationC"); } public class Facade { private readonly SubsystemA _subsystemA; private readonly SubsystemB _subsystemB; private readonly SubsystemC _subsystemC; public Facade() { _subsystemA = new SubsystemA(); _subsystemB = new SubsystemB(); _subsystemC = new SubsystemC(); } public void SimplifiedOperation() { _subsystemA.OperationA(); _subsystemB.OperationB(); _subsystemC.OperationC(); } } public class Client { public static void Main(string[] args) { Facade facade = new Facade(); facade.SimplifiedOperation(); } }
The Flyweight pattern involves creating a Flyweight factory that manages a pool of shared objects, optimizing memory usage by sharing objects.
using System; using System.Collections.Generic; public class Flyweight { private string intrinsicState; public Flyweight(string intrinsicState) { this.intrinsicState = intrinsicState; } public void Operation(string extrinsicState) { Console.WriteLine($"Intrinsic State: {intrinsicState}, Extrinsic State: {extrinsicState}"); } } public class FlyweightFactory { private Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>(); public Flyweight GetFlyweight(string key) { if (!flyweights.ContainsKey(key)) { flyweights[key] = new Flyweight(key); } return flyweights[key]; } } public class Client { public static void Main(string[] args) { FlyweightFactory factory = new FlyweightFactory(); Flyweight flyweight1 = factory.GetFlyweight("A"); flyweight1.Operation("First Call"); Flyweight flyweight2 = factory.GetFlyweight("A"); flyweight2.Operation("Second Call"); Flyweight flyweight3 = factory.GetFlyweight("B"); flyweight3.Operation("Third Call"); } }
The Proxy pattern controls access to an object by creating a proxy class that represents the original object, adding additional behavior like access control or logging.
public interface ISubject { void Request(); } public class RealSubject : ISubject { public void Request() { Console.WriteLine("RealSubject: Handling Request."); } } public class Proxy : ISubject { private RealSubject _realSubject; public void Request() { if (_realSubject == null) { _realSubject = new RealSubject(); } Console.WriteLine("Proxy: Logging access."); _realSubject.Request(); } } class Program { static void Main(string[] args) { ISubject proxy = new Proxy(); proxy.Request(); } }
The Chain of Responsibility pattern allows a request to pass through a chain of handlers, where each handler either processes the request or passes it to the next.
public abstract class Handler { protected Handler nextHandler; public void SetNextHandler(Handler handler) { nextHandler = handler; } public abstract void HandleRequest(string request); } public class ConcreteHandler1 : Handler { public override void HandleRequest(string request) { if (request == "Request1") { Console.WriteLine("ConcreteHandler1 handled the request."); } else if (nextHandler != null) { nextHandler.HandleRequest(request); } } } public class ConcreteHandler2 : Handler { public override void HandleRequest(string request) { if (request == "Request2") { Console.WriteLine("ConcreteHandler2 handled the request."); } else if (nextHandler != null) { nextHandler.HandleRequest(request); } } } var handler1 = new ConcreteHandler1(); var handler2 = new ConcreteHandler2(); handler1.SetNextHandler(handler2); handler1.HandleRequest("Request1"); handler1.HandleRequest("Request2");
The Command pattern encapsulates a request as an object, allowing for parameterization of clients with queues, requests, and operations.
public interface ICommand { void Execute(); } public class Light { public void TurnOn() { Console.WriteLine("The light is on"); } public void TurnOff() { Console.WriteLine("The light is off"); } } public class TurnOnCommand : ICommand { private Light _light; public TurnOnCommand(Light light) { _light = light; } public void Execute() { _light.TurnOn(); } } public class TurnOffCommand : ICommand { private Light _light; public TurnOffCommand(Light light) { _light = light; } public void Execute() { _light.TurnOff(); } } public class RemoteControl { private ICommand _command; public void SetCommand(ICommand command) { _command = command; } public void PressButton() { _command.Execute(); } } public class Program { public static void Main(string[] args) { Light light = new Light(); ICommand turnOn = new TurnOnCommand(light); ICommand turnOff = new TurnOffCommand(light); RemoteControl remote = new RemoteControl(); remote.SetCommand(turnOn); remote.PressButton(); remote.SetCommand(turnOff); remote.PressButton(); } }
Dependency Injection (DI) is a design pattern that helps in creating loosely coupled code by injecting dependencies into a class rather than having the class create its own dependencies.
public interface IService { void Serve(); } public class Service : IService { public void Serve() { Console.WriteLine("Service Called"); } } public class Client { private readonly IService _service; public Client(IService service) { _service = service; } public void Start() { _service.Serve(); } } class Program { static void Main(string[] args) { IService service = new Service(); Client client = new Client(service); client.Start(); } }
The Template Method pattern involves creating an abstract class that defines the template method and the abstract methods that subclasses need to implement.
using System; abstract class DataProcessor { public void ProcessData() { ReadData(); Process(); SaveData(); } protected abstract void ReadData(); protected abstract void Process(); protected abstract void SaveData(); } class CsvDataProcessor : DataProcessor { protected override void ReadData() { Console.WriteLine("Reading data from CSV file."); } protected override void Process() { Console.WriteLine("Processing CSV data."); } protected override void SaveData() { Console.WriteLine("Saving processed data to CSV file."); } } class XmlDataProcessor : DataProcessor { protected override void ReadData() { Console.WriteLine("Reading data from XML file."); } protected override void Process() { Console.WriteLine("Processing XML data."); } protected override void SaveData() { Console.WriteLine("Saving processed data to XML file."); } } class Program { static void Main() { DataProcessor csvProcessor = new CsvDataProcessor(); csvProcessor.ProcessData(); DataProcessor xmlProcessor = new XmlDataProcessor(); xmlProcessor.ProcessData(); } }
The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
using System; public class Subject { public delegate void Notify(); public event Notify OnNotify; public void ChangeState() { Console.WriteLine("State has changed."); OnNotify?.Invoke(); } } public class Observer { public void Update() { Console.WriteLine("Observer has been notified."); } } public class Program { public static void Main() { Subject subject = new Subject(); Observer observer = new Observer(); subject.OnNotify += observer.Update; subject.ChangeState(); } }