Sobes.tech
Junior

What design patterns exist in programming?

sobes.tech AI

Answer from AI

Design patterns are divided into three main categories: creational, structural, and behavioral.

Creational Patterns:

  • Factory Method: Provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created.
    // Product interface
    public interface IProduct
    {
        void DoSomething();
    }
    
    // Concrete Product A
    public class ConcreteProductA : IProduct
    {
        public void DoSomething()
        {
            // Implementation
        }
    }
    
    // Concrete Product B
    public class ConcreteProductB : IProduct
    {
        public void DoSomething()
        {
            // Implementation
        }
    }
    
    // Abstract creator
    public abstract class Creator
    {
        // Factory method
        public abstract IProduct CreateProduct();
    
        public void AnOperation()
        {
            var product = CreateProduct();
            product.DoSomething();
        }
    }
    
    // Concrete creator A
    public class ConcreteCreatorA : Creator
    {
        public override IProduct CreateProduct()
        {
            return new ConcreteProductA();
        }
    }
    
    // Concrete creator B
    public class ConcreteCreatorB : Creator
    {
        public override IProduct CreateProduct()
        {
            return new ConcreteProductB();
        }
    }
    
  • Abstract Factory: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
  • Builder: Separates the construction of a complex object from its representation so that the same construction process can create different representations.
    // Product
    public class Car
    {
        public string Engine { get; set; }
        public string Wheels { get; set; }
        public string Body { get; set; }
    
        public void Display()
        {
            // Show car details
        }
    }
    
    // Builder interface
    public interface ICarBuilder
    {
        void BuildEngine();
        void BuildWheels();
        void BuildBody();
        Car GetCar();
    }
    
    // Concrete builder
    public class SportsCarBuilder : ICarBuilder
    {
        private Car car = new Car();
    
        public void BuildEngine()
        {
            car.Engine = "Sports Engine";
        }
    
        public void BuildWheels()
        {
            car.Wheels = "Sports Wheels";
        }
    
        public void BuildBody()
        {
            car.Body = "Sports Body";
        }
    
        public Car GetCar()
        {
            return car;
        }
    }
    
    // Director
    public class CarDirector
    {
        public void Construct(ICarBuilder builder)
        {
            builder.BuildEngine();
            builder.BuildWheels();
            builder.BuildBody();
        }
    }
    
  • Prototype: Specifies the kinds of objects to create using a prototypical instance and creates new objects by copying this prototype.
  • Singleton: Ensures a class has only one instance and provides a global point of access to it.

Structural Patterns:

  • Adapter: Converts the interface of a class into another interface clients expect. Adapter lets classes with incompatible interfaces work together.
    // Incompatible class (adaptee)
    public class Adaptee
    {
        public void SpecificRequest()
        {
            // Implementation of specific request
        }
    }
    
    // Target interface
    public interface ITarget
    {
        void Request();
    }
    
    // Adapter
    public class Adapter : ITarget
    {
        private readonly Adaptee _adaptee = new Adaptee();
    
        public void Request()
        {
            _adaptee.SpecificRequest();
        }
    }
    
  • Bridge: Decouples an abstraction from its implementation so that the two can vary independently.
  • Composite: Composes objects into tree structures to represent hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
  • Decorator: Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
  • Facade: Provides a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
    // Subsystem part 1
    public class SubsystemA
    {
        public void OperationA()
        {
            // ...
        }
    }
    
    // Subsystem part 2
    public class SubsystemB
    {
        public void OperationB()
        {
            // ...
        }
    }
    
    // Facade
    public class Facade
    {
        private readonly SubsystemA subsystemA = new SubsystemA();
        private readonly SubsystemB subsystemB = new SubsystemB();
    
        public void SimplifiedOperation()
        {
            subsystemA.OperationA();
            subsystemB.OperationB();
        }
    }
    
  • Flyweight: Uses sharing to support large numbers of fine-grained objects efficiently.
  • Proxy: Provides a surrogate or placeholder for another object to control access to it.

Behavioral Patterns:

  • Chain of Responsibility: Avoids coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
    public abstract class Handler
    {
        protected Handler _nextHandler;
    
        public void SetNext(Handler handler)
        {
            _nextHandler = handler;
        }
    
        public abstract void HandleRequest(int request);
    }
    
    public class ConcreteHandler1 : Handler
    {
        public override void HandleRequest(int request)
        {
            if (request >= 0 && request < 10)
            {
                // Handle request
            }
            else if (_nextHandler != null)
            {
                _nextHandler.HandleRequest(request);
            }
        }
    }
    
    public class ConcreteHandler2 : Handler
    {
        public override void HandleRequest(int request)
        {
            if (request >= 10 && request < 20)
            {
                // Handle request
            }
            else if (_nextHandler != null)
            {
                _nextHandler.HandleRequest(request);
            }
        }
    }
    
  • Command: Encapsulates a request as an object, thereby allowing for parameterization of clients with different requests, queuing of requests, and logging of operations.
  • Iterator: Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
  • Mediator: Defines an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.
  • Memento: Without violating encapsulation, captures and externalizes an object's internal state so that the object can be restored to this state later.
  • Observer: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
    public interface IObserver
    {
        void Update(string message);
    }
    
    public class Subject
    {
        private List<IObserver> _observers = new List<IObserver>();
    
        public void Attach(IObserver observer)
        {
            _observers.Add(observer);
        }
    
        public void Detach(IObserver observer)
        {
            _observers.Remove(observer);
        }
    
        public void Notify(string message)
        {
            foreach (var observer in _observers)
            {
                observer.Update(message);
            }
        }
    
        public void SomeBusinessLogic()
        {
            // ...
            Notify("State changed");
            // ...
        }
    }
    
    public class ConcreteObserver : IObserver
    {
        public void Update(string message)
        {
            // Handle notification
        }
    }
    
  • State: Allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
  • Strategy: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
  • Template Method: Defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing its structure.
  • Visitor: Represents an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
    // Visitor interface
    public interface IVisitor
    {
        void Visit(ElementA element);
        void Visit(ElementB element);
    }
    
    // Element interface
    public interface IElement
    {
        void Accept(IVisitor visitor);
    }
    
    // Concrete element A
    public class ElementA : IElement
    {
        public void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    
        public string OperationA()
        {
            return "ElementA";
        }
    }
    
    // Concrete element B
    public class ElementB : IElement
    {
        public void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    
        public string OperationB()
        {
            return "ElementB";
        }
    }
    
    // Concrete visitor
    public class ConcreteVisitor1 : IVisitor
    {
        public void Visit(ElementA element)
        {
            // Action for ElementA
        }
    
        public void Visit(ElementB element)
        {
            // Action for ElementB
        }
    }