Post Details

Design Patterns in C Sharp .Net with examples | Singleton Design Patterns

np

Fri , Aug 04 2023

np

This is very common Question asked in interviews in any big organizations like TCS, Infosys.

Most commonly used design patterns in C# with examples:

Creational Design Patterns

Creational design patterns provide ways to instantiate a single object or group of related objects. These patterns deal with the object creation process in such a way that they are separated from their implementing system. That provides more flexibility in deciding which object needs to be created or instantiated for a given scenario. There are the following five such patterns:

  • Abstract Factory: This pattern provides an interface for creating families of related objects, without specifying their concrete classes. For example, let’s say you have a Button class and a TextBox class, and you want to create a WindowsButton class and a WindowsTextBox class for Windows systems, and a MacButton class and a MacTextBox class for Mac systems. You can use the abstract factory pattern to create two factories: one for Windows systems and one for Mac systems. Each factory will create the appropriate Button and TextBox classes for its system.

public interface IGuiFactory

{

    IButton CreateButton();

    ITextBox CreateTextBox();

}


public interface IButton

{

    void Paint();

}


public interface ITextBox

{

    void Paint();

}


public class WindowsGuiFactory : IGuiFactory

{

    public IButton CreateButton()

    {

        return new WindowsButton();

    }


    public ITextBox CreateTextBox()

    {

        return new WindowsTextBox();

    }

}


public class MacGuiFactory : IGuiFactory

{

    public IButton CreateButton()

    {

        return new MacButton();

    }


    public ITextBox CreateTextBox()

    {

        return new MacTextBox();

    }

}


public class WindowsButton : IButton

{

    public void Paint()

    {

        Console.WriteLine("Painting Windows button...");

    }

}


public class WindowsTextBox : ITextBox

{

    public void Paint()

    {

        Console.WriteLine("Painting Windows text box...");

    }

}


public class MacButton : IButton

{

    public void Paint()

    {

        Console.WriteLine("Painting Mac button...");

    }

}


public class MacTextBox : ITextBox

{

    public void Paint()

    {

        Console.WriteLine("Painting Mac text box...");

    }

}


  • Builder: This pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. For example, let’s say you have a Pizza class that has many different toppings and sizes. You can use the builder pattern to create a PizzaBuilder class that allows you to specify the toppings and size of the pizza, and then build the pizza using those specifications.

public class Pizza

{

    private readonly List _toppings = new List();

    private readonly int _size;


    public Pizza(int size)

    {

        _size = size;

    }


    public void AddTopping(string topping)

    {

        _toppings.Add(topping);

    }


    public override string ToString()

    {

        var sb = new StringBuilder();

        sb.Append($"Size: {_size} inches\n");

        sb.Append("Toppings: ");

        foreach (var topping in _toppings)

        {

            sb.Append($"{topping}, ");

        }

        sb.Remove(sb.Length - 2, 2);

        return sb.ToString();

    }

}


public interface IPizzaBuilder

{

    void SetSize(int size);

    void AddTopping(string topping);

}


public class PizzaBuilder : IPizzaBuilder

{

    private readonly Pizza _pizza;


    public PizzaBuilder()

    {

        _pizza = new Pizza(0);

    }


    public void SetSize(int size)

    {

        _pizza.Size = size;

    }


    public void AddTopping(string topping)

    {

        _pizza.AddTopping(topping);

    }


    public Pizza Build()

    {

        return _pizza;

    }

}


  • Factory Method: This pattern provides an interface for creating objects, but allows subclasses to decide which class to instantiate. For example, let’s say you have an abstract Animal class and two concrete classes: Dog and Cat. You can use the factory method pattern to create two factories: one for creating dogs and one for creating cats. Each factory will return an instance of the appropriate concrete class.

public abstract class Animal

{

}


public class Dog : Animal

{

}


public class Cat : Animal

{

}


public interface IAnimalFactory

{

   Animal CreateAnimal();

}


public class DogFactory : IAnimalFactory

{

   public Animal CreateAnimal()

   {

      return new Dog();

   }

}


public class CatFactory : IAnimalFactory

{

   public Animal CreateAnimal()

   {

      return new Cat();

   }

}


  • Prototype: This pattern creates new objects by cloning existing ones, rather than by instantiating new ones from scratch. For example, let’s say you have a Person class that has many different properties such as name, age, address, etc. You can use the prototype pattern to create a new person by cloning an existing person with similar properties.
  • Singleton: This pattern ensures that a class has only one instance, and provides a global point of access to that instance. For example, let’s say you have a Logger class that logs messages to a file. You can use the singleton pattern to ensure that there is only one instance of the logger throughout your program.

using System;


public sealed class Singleton

{

    private static Singleton instance = null;

    private static readonly object padlock = new object();


    Singleton()

    {

    }


    public static Singleton Instance

    {

        get

        {

            lock (padlock)

            {

                if (instance == null)

                {

                    instance = new Singleton();

                }

                return instance;

            }

        }

    }

}


class Program

{

    static void Main()

    {

        Singleton s1 = Singleton.Instance;

        Singleton s2 = Singleton.Instance;


        if (s1 == s2)

        {

            Console.WriteLine("Only one instance exists.");

        }


        Console.ReadKey();

    }

}


Structural Design Patterns

Structural design patterns deal with object composition and provide ways to combine objects to form new structures. These patterns help make your code more flexible and modular. There are the following seven such patterns:

  • Adapter: This pattern allows incompatible interfaces to work together by wrapping one interface around another. For example, let’s say you have an existing LegacyRectangle class that has methods called Draw() and Resize(), but you need to use it in your code with an interface called Shape. You can use the adapter pattern to create a new class called RectangleAdapter that implements the Shape interface and wraps around the existing LegacyRectangle class.
  • Bridge: This pattern separates an object’s interface from its implementation, allowing them to vary independently. For example, let’s say you have an abstract Shape class and two concrete classes: Circle and Square. You can use the bridge pattern to create two hierarchies: one for shapes with red borders and one for shapes with blue borders.
  • Composite: This pattern allows you to treat a group of objects as if they were a single object. For example, let’s say you have an abstract Component class and two concrete classes: Leaf and Composite. The leaf represents individual objects while composite represents groups of objects.
  • Decorator: This pattern allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. For example, let’s say you have an abstract Beverage class and two concrete classes: Coffee and Tea. You can use the decorator pattern to add condiments such as milk or sugar to each beverage.
  • Facade: This pattern provides a simplified interface to a complex system of classes, making it easier to use. For example, let’s say you have many different classes that are needed to perform some complex task such as sending an email. You can use the facade pattern to create a new class called EmailSender that hides all of the complexity behind a simple interface.
  • Flyweight: This pattern allows you to share common parts of objects between multiple objects, reducing memory usage and improving performance. For example, let’s say you have many different objects that all need access to some common data such as colors or fonts. You can use the flyweight pattern to store this data in shared memory so that it can be reused by multiple objects without creating new instances.
  • Proxy: This pattern provides a placeholder for another object, allowing you to control access to the original object. For example, let’s say you have an object that performs some expensive or sensitive operation such as accessing a remote server or a database. You can use the proxy pattern to create a new object that acts as a proxy for the original object, and performs some additional functionality such as caching, logging, or security checks before delegating the request to the original object.

Behavioral Design Patterns

Behavioral design patterns deal with communication between objects and provide ways of organizing and managing complex systems. These patterns help make your code more flexible and maintainable. There are the following eleven such patterns:

  • Chain of Responsibility: This pattern allows you to chain together multiple handlers for a request, each handling the request if possible or passing it on to the next handler in the chain. For example, let’s say you have a program that handles different types of requests such as logging, authentication, validation, etc. You can use the chain of responsibility pattern to create a series of handlers that each handle a specific type of request or pass it on to the next handler if they cannot handle it.
  • Command: This pattern encapsulates a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations. For example, let’s say you have a program that performs different types of actions such as opening a file, saving a file, copying text, etc. You can use the command pattern to create an abstract Command class and several concrete classes that implement the Command interface and perform specific actions. You can then use these command objects to execute different actions or store them in a history list for undoing or redoing them later.
  • Interpreter: This pattern defines a grammar for interpreting a language and provides an interpreter for that language. For example, let’s say you want to create your own language or DSL (domain-specific language) for performing some specific task such as querying a database or drawing shapes. You can use the interpreter pattern to define a grammar for your language using classes that represent different expressions or statements in your language. You can then use these classes to parse and interpret your language.
  • Iterator: This pattern provides a way of accessing elements of an aggregate object sequentially without exposing its underlying representation. For example, let’s say you have a collection of objects such as an array, a list, or a tree. You can use the iterator pattern to create an abstract Iterator interface and several concrete classes that implement the Iterator interface and provide different ways of traversing the collection such as forward, backward, depth-first, breadth-first, etc.
  • Mediator: This pattern defines an object that encapsulates how a set of objects interact with each other, allowing them to communicate through the mediator instead of directly with each other. For example, let’s say you have a program that has many different components such as buttons, text boxes, labels, etc. You can use the mediator pattern to create a new class called Mediator that coordinates the communication between these components and reduces their coupling.
  • Memento: This pattern allows you to capture and restore an object’s internal state without violating encapsulation. For example, let’s say you have an object that has many different properties such as name, age, address, etc. You can use the memento pattern to create a new class called Memento that stores the state of the object at some point in time. You can then use this memento object to restore the state of the original object later.
  • Observer: This pattern defines a one-to-many dependency between objects, so that when one object changes state, all its dependents are notified and updated automatically. For example, let’s say you have an object that represents some data such as temperature or stock price. You can use the observer pattern to create an abstract Observer interface and several concrete classes that implement the Observer interface and perform some action when they are notified of changes in the data. You can then register these observers with the data object and notify them whenever the data changes.
  • State: This pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class. For example, let’s say you have an object that represents a traffic light. You can use the state pattern to create an abstract State interface and several concrete classes that implement the State interface and represent different states of the traffic light such as red, green, yellow, etc. You can then change the state of the traffic light object depending on some condition such as time or sensor input.
  • Strategy: This pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. The strategy lets the algorithm vary independently from clients that use it. For example, let’s say you have an object that performs some calculation such as sorting or searching. You can use the strategy pattern to create an abstract Strategy interface and several concrete classes that implement the Strategy interface and provide different algorithms for the calculation. You can then use these strategy objects to perform the calculation depending on some criteria such as speed or accuracy.
  • Template Method: This pattern defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. The template method lets subclasses redefine certain steps of an algorithm without changing its structure. For example, let’s say you have an abstract class that performs some task such as downloading a file or processing a payment. You can use the template method pattern to define the common steps of the task in the abstract class and leave some steps to be implemented by subclasses that provide different implementations for those steps.
  • Visitor: This pattern defines a new operation to a class of objects without changing the class. The visitor lets you define a new operation without changing the classes of the elements on which it operates. For example, let’s say you have a class hierarchy that represents different types of shapes such as circles, squares, triangles, etc. You can use the visitor pattern to create an abstract Visitor interface and several concrete classes that implement the Visitor interface and provide different operations on the shapes such as drawing, resizing, calculating area, etc.

These are some of the most commonly used design patterns in C#. Each pattern has its own strengths and weaknesses, and should be used appropriately depending on the specific problem you are trying to solve.


Leave a Reply

Please log in to Comment On this post.