Design Patterns in C#

Design Patterns in C#

30 Sep 2024
Beginner
415 Views
83 min read
Learn with an interactive course and practical hands-on labs

⭐ .NET Design Patterns Course: Design Patterns in C# Online Training

Design Patterns in C#

Design patterns in C# are an important concept in software development. They provide tested answers to common design issues, helping developers reduce code structure and improve maintainability. Design patterns allow you to construct resilient and scalable systems while also supporting excellent software architectural practices.

In the Design Pattern tutorial, we'll learn about what are design patterns in C#, the types of design patterns in C#, when to use them, and how they might help you write better code. Let us begin by examining "What are Design Patterns?"

What are Design Patterns?

  • A design pattern is a general, recurring solution to a commonly encountered problem in software design that is utilized in software engineering.
  • It is not a comprehensive design that can be implemented in code straight immediately.
  • It is a description or model for issue solving that may be used in a variety of settings.
  • Design patterns are similar to cooking recipes in that they provide a tried-and-true approach to producing consistent results without having to start from scratch each time.
Read More:
Different Types of Software Design Principles
What is an IoC Container or DI Container

Types of Design Patterns

There are three types of Design Patterns that we have:
  1. Creational Patterns
  2. Structural Patterns
  3. Behavioral Patterns
Now, we will discuss each of them step by step:

1. Creational Design Patterns

  • Creational design patterns are one category of design patterns used in software development.
  • They work on improving the flexibility and efficiency of the object production process.
  • Creational design patterns define ways to create objects.
Read More: 7 Phases of the Software Development Life Cycle

Creational Design Patterns

  • The aim of these design patterns is not to compose objects directly but to introduce different ways for object creation so as to make the code easier and more flexible.
  • Using creational design patterns, developers can maintain the creation process in one place, separately from other code, thus enabling easy maintenance and upgrade of a program.
  • They basically help in building systems where the way things are created does not interfere with the overall design.

There are five types of creational design patterns:

  1. Singleton Design Pattern
  2. Factory Method Design Pattern
  3. Abstract Factory Design Pattern
  4. Builder Design Pattern
  5. Prototype Design Pattern

Let's start with a singleton design pattern.

1. Singleton design pattern

  • Singleton Design Pattern ensures that a class has only one instance throughout the application and provides a global point to access it.
  • This is useful when you need exactly one object, such as a configuration manager, to coordinate actions across the system.

Example

 using System;

// Singleton class
public class ScholarHatSingleton
{
    // Single instance of the class, initially set to null
    private static ScholarHatSingleton instance;
    // Private constructor to prevent instantiation from outside the class
    private ScholarHatSingleton() { }
    // Public method to provide access to the single instance of the class
    public static ScholarHatSingleton GetInstance()
    {
        // Check if instance is null; if it is, create a new instance
        if (instance == null)
        {
            instance = new ScholarHatSingleton();
        }
        // Return the single instance
        return instance;
    }
    // Method to display a message
    public void ShowMessage()
    {
        Console.WriteLine("Welcome to ScholarHat Singleton!");
    }
}
// Main class
public class SingletonExample
{
    public static void Main(string[] args)
    {
        // Getting the single instance of ScholarHatSingleton
        ScholarHatSingleton singleton = ScholarHatSingleton.GetInstance();
        // Displaying the message
        singleton.ShowMessage();
    }
}   

Output

 Welcome to ScholarHat Singleton!

Explanation

In this example

  • we declare a class named ScholarHatSingleton, which uses a private constructor and a static  variableto hold the instance, allowing for the creation of only one instance.
  • The GetInstance() method checks for the existing instance; if none exists, it creates one, ensuring consistent access throughout the application.
  • The ShowMessage() method displays a welcome message, demonstrating how to use the singleton instance in the Main method.

2. Factory Method Design Pattern

  • Factory Method Design is a method for creating objects but let subclasses decide which class to instantiate.
  • This pattern is useful when you need to create objects from a family of related classes. It allows you to use a common interface while delegating the actual creation to subclasses.
  • For example, a document editor may use a factory method to create different types of documents (e.g., text, spreadsheet) based on user input without knowing the specific class of the document being created.

Example

 using System;

// Course interface
public interface ICourse
{
    void GetCourseDetails();
}

// Java course implementation
public class JavaCourse : ICourse
{
    public void GetCourseDetails()
    {
        Console.WriteLine("This is a Java course.");
    }
}

// Python course implementation
public class PythonCourse : ICourse
{
    public void GetCourseDetails()
    {
        Console.WriteLine("This is a Python course.");
    }
}

// Course factory
public static class CourseFactory
{
    public static ICourse GetCourse(string courseType)
    {
        if (string.IsNullOrEmpty(courseType))
        {
            return null;
        }
        if (courseType.Equals("JAVA", StringComparison.OrdinalIgnoreCase))
        {
            return new JavaCourse();
        }
        else if (courseType.Equals("PYTHON", StringComparison.OrdinalIgnoreCase))
        {
            return new PythonCourse();
        }
        return null;
    }
}

// Main class
public class FactoryPatternExample
{
    public static void Main(string[] args)
    {
        // Creating Java course using Factory
        ICourse javaCourse = CourseFactory.GetCourse("JAVA");
        javaCourse?.GetCourseDetails();

        // Creating Python course using Factory
        ICourse pythonCourse = CourseFactory.GetCourse("PYTHON");
        pythonCourse?.GetCourseDetails();
    }
}  

Output

 This is a Java course.
This is a Python course.

Explanation

In this example,

  • The ICourse interface defines a method GetCourseDetails() that must be implemented by all course types.
  • The JavaCourse and PythonCourse classes implement the ICourse interface, providing specific details for each course.
  • The static CourseFactory.GetCourse method creates and returns an instance of a course based on the input string, supporting case-insensitive checks.
  • The Main method demonstrates creating course instances via the factory and calling GetCourseDetails() to display information for each course.

3. Abstract Factory design Pattern

  • Abstract Factory Design Pattern provides an interface for creating families of related objects without specifying their concrete classes.
  • This pattern is useful when you need to create objects that are part of a larger group, ensuring that the objects fit together well.

Example

 using System;
// Abstract Product Interfaces
interface ICourse {
    void GetCourseDetails();
}

interface ICertification {
    void GetCertificationDetails();
}

// Concrete Products for Java
class JavaCourse : ICourse {
    public void GetCourseDetails() {
        Console.WriteLine("ScholarHat offers an in-depth Java course.");
    }
}

class JavaCertification : ICertification {
    public void GetCertificationDetails() {
        Console.WriteLine("ScholarHat provides Java Certification after course completion.");
    }
}

// Concrete Products for Python
class PythonCourse : ICourse {
    public void GetCourseDetails() {
        Console.WriteLine("ScholarHat offers a comprehensive Python course.");
    }
}

class PythonCertification : ICertification {
    public void GetCertificationDetails() {
        Console.WriteLine("ScholarHat provides Python Certification after course completion.");
    }
}

// Abstract Factory Interface
interface ICourseFactory {
    ICourse CreateCourse();
    ICertification CreateCertification();
}

// Concrete Factories
class JavaFactory : ICourseFactory {
    public ICourse CreateCourse() {
        return new JavaCourse();
    }

    public ICertification CreateCertification() {
        return new JavaCertification();
    }
}

class PythonFactory : ICourseFactory {
    public ICourse CreateCourse() {
        return new PythonCourse();
    }

    public ICertification CreateCertification() {
        return new PythonCertification();
    }
}

// Main Class
class Program {
    static void Main(string[] args) {
        // Creating Java factory
        ICourseFactory javaFactory = new JavaFactory();
        ICourse javaCourse = javaFactory.CreateCourse();
        ICertification javaCertification = javaFactory.CreateCertification();
        javaCourse.GetCourseDetails();
        javaCertification.GetCertificationDetails();

        // Creating Python factory
        ICourseFactory pythonFactory = new PythonFactory();
        ICourse pythonCourse = pythonFactory.CreateCourse();
        ICertification pythonCertification = pythonFactory.CreateCertification();
        pythonCourse.GetCourseDetails();
        pythonCertification.GetCertificationDetails();
    }
}    

Output

 ScholarHat offers an in-depth Java course.
ScholarHat provides Java Certification after course completion.
ScholarHat offers a comprehensive Python course.
ScholarHat provides Python Certification after course completion.

Explanation

  • Abstract Factory Pattern: The pattern allows the creation of related products (like courses and certifications) without specifying their concrete implementations.
  • Interfaces for Products: ICourse and ICertification define the structure for course and certification, implemented by concrete classes (JavaCourse, PythonCourse, etc.).
  • Factories Create Products: ICourseFactory declares methods for creating a course and certification, and the JavaFactory and PythonFactory classes provide specific implementations.
  • Main Method: In Main(), factories are used to create Java and Python course-related products, demonstrating the use of the Abstract Factory Pattern.

4. Builder Design Pattern

  • Builder Pattern separates the process of constructing a complex object from its representation, allowing you to build different types of objects using the same construction process.
  • This pattern is helpful when creating complex objects with many optional parts, making the code cleaner and more manageable.

Example

 using System;

// Product Class
class ScholarHatCourse {
    private string name;
    private string duration;
    private string level;

    // Constructor (private to enforce the builder pattern)
    private ScholarHatCourse(CourseBuilder builder) {
        this.name = builder.Name;
        this.duration = builder.Duration;
        this.level = builder.Level;
    }

    // Getters
    public string GetName() {
        return name;
    }

    public string GetDuration() {
        return duration;
    }

    public string GetLevel() {
        return level;
    }

    // Static Builder Class
    public class CourseBuilder {
        public string Name { get; private set; }
        public string Duration { get; private set; }
        public string Level { get; private set; }

        public CourseBuilder SetName(string name) {
            this.Name = name;
            return this;
        }

        public CourseBuilder SetDuration(string duration) {
            this.Duration = duration;
            return this;
        }

        public CourseBuilder SetLevel(string level) {
            this.Level = level;
            return this;
        }

        public ScholarHatCourse Build() {
            return new ScholarHatCourse(this);
        }
    }
}

// Main Class
class BuilderPatternExample {
    static void Main(string[] args) {
        // Creating a ScholarHat course using the Builder Pattern
        ScholarHatCourse javaCourse = new ScholarHatCourse.CourseBuilder()
            .SetName("Java Programming")
            .SetDuration("6 months")
            .SetLevel("Intermediate")
            .Build();
        
        // Displaying course details
        Console.WriteLine("Course Name: " + javaCourse.GetName());
        Console.WriteLine("Duration: " + javaCourse.GetDuration());
        Console.WriteLine("Level: " + javaCourse.GetLevel());

        // Creating another course
        ScholarHatCourse pythonCourse = new ScholarHatCourse.CourseBuilder()
            .SetName("Python Programming")
            .SetDuration("4 months")
            .SetLevel("Beginner")
            .Build();

        // Displaying course details
        Console.WriteLine("\nCourse Name: " + pythonCourse.GetName());
        Console.WriteLine("Duration: " + pythonCourse.GetDuration());
        Console.WriteLine("Level: " + pythonCourse.GetLevel());
    }
}   

Output

 Course Name: Java Programming
Duration: 6 months
Level: Intermediate

Course Name: Python Programming
Duration: 4 months
Level: Beginner

Explanation

  • Private Constructor: ScholarHatCourse has a private constructor that is only accessible through the static CourseBuilder class, enforcing the builder pattern.
  • Builder Class: The inner CourseBuilder class provides methods to set properties (Name, Duration, Level) and a Build() method to create the ScholarHatCourse object.
  • Main Method: In Main(), two courses (Java and Python) are created using the builder pattern, and their details are printed.

5. Prototype Design Pattern

  • Prototype Pattern creates new objects by copying an existing object, known as the prototype, rather than building them from scratch.
  • This is useful when object creation is costly or complex, allowing for faster and more efficient object creation.

Example

 using System;
// Abstract Prototype Class
abstract class ScholarHatCourse : ICloneable {
    private string courseName;
    private string courseLevel;

    // Getters and Setters
    public string CourseName {
        get { return courseName; }
        set { courseName = value; }
    }

    public string CourseLevel {
        get { return courseLevel; }
        set { courseLevel = value; }
    }

    // Abstract method to clone the object
    public abstract object Clone();

    // Method to display course details
    public void ShowCourseDetails() {
        Console.WriteLine("Course Name: " + courseName);
        Console.WriteLine("Course Level: " + courseLevel);
    }
}

// Concrete Prototype Class for Java Course
class JavaCourse : ScholarHatCourse {
    public JavaCourse() {
        this.CourseName = "Java Programming";
        this.CourseLevel = "Intermediate";
    }

    public override object Clone() {
        return this.MemberwiseClone();
    }
}

// Concrete Prototype Class for Python Course
class PythonCourse : ScholarHatCourse {
    public PythonCourse() {
        this.CourseName = "Python Programming";
        this.CourseLevel = "Beginner";
    }

    public override object Clone() {
        return this.MemberwiseClone();
    }
}

// Main Class
class PrototypePatternExample {
    static void Main(string[] args) {
        // Creating original Java course
        JavaCourse javaCourse = new JavaCourse();
        javaCourse.ShowCourseDetails();

        // Cloning the Java course
        ScholarHatCourse clonedJavaCourse = (ScholarHatCourse)javaCourse.Clone();
        clonedJavaCourse.ShowCourseDetails();

        // Creating original Python course
        PythonCourse pythonCourse = new PythonCourse();
        pythonCourse.ShowCourseDetails();

        // Cloning the Python course
        ScholarHatCourse clonedPythonCourse = (ScholarHatCourse)pythonCourse.Clone();
        clonedPythonCourse.ShowCourseDetails();
    }
}   

Output

 Course Name: Java Programming
Course Level: Intermediate
Course Name: Java Programming
Course Level: Intermediate
Course Name: Python Programming
Course Level: Beginner
Course Name: Python Programming
Course Level: Beginner

Explanation

  • Prototype Pattern: This pattern allows creating new objects by copying an existing object (cloning), avoiding costly object creation.
  • Cloning: The Clone() method uses shallow cloning via MemberwiseClone() to create new course objects.
  • Main Method: Java and Python course objects are created, displayed, and then cloned to demonstrate copying functionality.

2. Structural Patterns

  • Structural design patterns are a type of design pattern in software development that focuses on combining classes or objects to construct larger, more complicated structures.

Structural Patterns

  • They aid in organizing and managing object interactions in software systems, resulting in increased flexibility, reusability, and maintainability.

There are seven types of Structural design patterns.

  1. Adapter Design Pattern
  2. Bridge Design Pattern
  3. Composite Design Pattern
  4. Decorator Design Pattern
  5. Facade Design Pattern
  6. Flyweight Design Pattern
  7. Proxy Design Pattern
Let's explore it one by one:

1. Adapter Pattern Design Pattern

  • Adapter Pattern allows incompatible interfaces to work together by wrapping one interface with another.
  • This pattern is like a translator that enables two systems with different languages to communicate seamlessly.

Example

 using System;

// Target Interface
interface IScholarHatCourse {
    void GetCourseDetails();
}

// Adaptee Class (Existing Class)
class PythonCourseDetails {
    public void DisplayCourseDetails() {
        Console.WriteLine("ScholarHat offers a comprehensive Python course.");
    }
}

// Adapter Class
class PythonCourseAdapter : IScholarHatCourse {
    private PythonCourseDetails _pythonCourseDetails;

    public PythonCourseAdapter(PythonCourseDetails pythonCourseDetails) {
        _pythonCourseDetails = pythonCourseDetails;
    }

    public void GetCourseDetails() {
        _pythonCourseDetails.DisplayCourseDetails();
    }
}

// Main Class
class AdapterPatternExample {
    static void Main(string[] args) {
        // Existing Python course details (Adaptee)
        PythonCourseDetails pythonDetails = new PythonCourseDetails();

        // Adapter makes PythonCourseDetails compatible with IScholarHatCourse interface
        IScholarHatCourse courseAdapter = new PythonCourseAdapter(pythonDetails);

        // Using the adapter to get course details
        courseAdapter.GetCourseDetails();
    }
}  

Output

 ScholarHat offers a comprehensive Python course.

Explanation

  • Adapter Pattern: The pattern allows converting an existing class (Adaptee) to be compatible with a different interface (Target).
  • Target Interface: IScholarHatCourse defines the method GetCourseDetails() that the client expects.
  • Adapter Class: PythonCourseAdapter adapts PythonCourseDetails (Adaptee) to work with IScholarHatCourse by calling the DisplayCourseDetails() method.
  • Main Method: The adapter (PythonCourseAdapter) bridges PythonCourseDetails with IScholarHatCourse so the existing class can be used in a new system.

2. Bridge Pattern Design Pattern

  • A Bridge Pattern separates an object’s abstraction from its implementation, allowing it to vary independently.
  • This pattern is useful when you want to decouple a system so that both the abstraction and implementation can evolve without affecting each other.

Example

 using System;
// Implementor Interface
interface ICourseContent {
    void GetContent();
}

// Concrete Implementor 1
class VideoContent : ICourseContent {
    public void GetContent() {
        Console.WriteLine("ScholarHat provides video content for the course.");
    }
}

// Concrete Implementor 2
class ArticleContent : ICourseContent {
    public void GetContent() {
        Console.WriteLine("ScholarHat provides article content for the course.");
    }
}

// Abstraction
abstract class ScholarHatCourse {
    protected ICourseContent content;

    public ScholarHatCourse(ICourseContent content) {
        this.content = content;
    }

    public abstract void ShowCourseDetails();
}

// Refined Abstraction 1
class JavaCourse : ScholarHatCourse {
    public JavaCourse(ICourseContent content) : base(content) { }

    public override void ShowCourseDetails() {
        Console.WriteLine("Java Programming Course:");
        content.GetContent();
    }
}

// Refined Abstraction 2
class PythonCourse : ScholarHatCourse {
    public PythonCourse(ICourseContent content) : base(content) { }

    public override void ShowCourseDetails() {
        Console.WriteLine("Python Programming Course:");
        content.GetContent();
    }
}

// Main Class
class BridgePatternExample {
    static void Main(string[] args) {
        // Creating a Java course with video content
        ScholarHatCourse javaCourse = new JavaCourse(new VideoContent());
        javaCourse.ShowCourseDetails();

        // Creating a Python course with article content
        ScholarHatCourse pythonCourse = new PythonCourse(new ArticleContent());
        pythonCourse.ShowCourseDetails();
    }
}  
 Java Programming Course:
ScholarHat provides video content for the course.
Python Programming Course:
ScholarHat provides article content for the course.

Explanation

  • Bridge Pattern: The pattern decouples the abstraction (ScholarHatCourse) from its implementation (ICourseContent), allowing both to vary independently.
  • Implementor Interface: ICourseContent defines how content is provided, with concrete implementors (VideoContent and ArticleContent) providing specific types of content.
  • Abstraction & Refined Abstractions: ScholarHatCourse is an abstract class that is extended by JavaCourse and PythonCourse. They bridge course details with content types at runtime, enabling flexible content delivery.

3. Composite Pattern Design Pattern

  • Composite Pattern lets you compose objects into tree structures to represent part-whole hierarchies.
  • This pattern allows clients to treat individual objects and compositions of objects uniformly, simplifying the handling of complex structures.

Example

 using System;
using System.Collections.Generic;

// Component Interface
interface IScholarHatCourse {
    void ShowCourseDetails();
}

// Leaf Class 1
class SingleCourse : IScholarHatCourse {
    private string courseName;

    public SingleCourse(string courseName) {
        this.courseName = courseName;
    }

    public void ShowCourseDetails() {
        Console.WriteLine("Course: " + courseName);
    }
}

// Composite Class
class CourseBundle : IScholarHatCourse {
    private List courses = new List();

    public void AddCourse(IScholarHatCourse course) {
        courses.Add(course);
    }

    public void RemoveCourse(IScholarHatCourse course) {
        courses.Remove(course);
    }

    public void ShowCourseDetails() {
        foreach (var course in courses) {
            course.ShowCourseDetails();
        }
    }
}

// Main Class
class CompositePatternExample {
    static void Main(string[] args) {
        // Single courses (Leafs)
        IScholarHatCourse javaCourse = new SingleCourse("Java Programming");
        IScholarHatCourse pythonCourse = new SingleCourse("Python Programming");
        IScholarHatCourse dataScienceCourse = new SingleCourse("Data Science");

        // Course bundle (Composite)
        CourseBundle programmingBundle = new CourseBundle();
        programmingBundle.AddCourse(javaCourse);
        programmingBundle.AddCourse(pythonCourse);

        // Full bundle including programming and data science courses
        CourseBundle fullBundle = new CourseBundle();
        fullBundle.AddCourse(programmingBundle);
        fullBundle.AddCourse(dataScienceCourse);

        // Display details of the full bundle
        Console.WriteLine("ScholarHat Full Course Bundle:");
        fullBundle.ShowCourseDetails();
    }
} 

Output

 ScholarHat Full Course Bundle:
Course: Java Programming
Course: Python Programming
Course: Data Science

Explanation

  • Composite Pattern: This pattern allows for the uniform treatment of individual objects (SingleCourse) and compositions of objects (CourseBundle) through the IScholarHatCourse interface.
  • Leaf and Composite: SingleCourse is a leaf that represents individual courses, while CourseBundle is a composite that can contain multiple courses, allowing hierarchical grouping.
  • Main Class: The CompositePatternExample shows how you can build bundles of courses (e.g., programming courses) and further bundle them into a full course collection, treating individual courses and bundles the same.

4. Decorator Pattern Design Pattern

  • Decorator Pattern adds additional responsibilities to an object dynamically without altering its structure.
  • This pattern is useful when you want to enhance or modify the behavior of objects at runtime without changing their code.

Example

 using System;

// Base Interface
interface ICourse {
    string GetDescription();
    double GetCost();
}

// Concrete Component
class BasicCourse : ICourse {
    public string GetDescription() {
        return "Basic Course";
    }

    public double GetCost() {
        return 100.00;
    }
}

// Decorator
abstract class CourseDecorator : ICourse {
    protected ICourse course;

    public CourseDecorator(ICourse course) {
        this.course = course;
    }

    public virtual string GetDescription() {
        return course.GetDescription();
    }

    public virtual double GetCost() {
        return course.GetCost();
    }
}

// Concrete Decorator 1
class PremiumContentDecorator : CourseDecorator {
    public PremiumContentDecorator(ICourse course) : base(course) { }

    public override string GetDescription() {
        return course.GetDescription() + " with Premium Content";
    }

    public override double GetCost() {
        return course.GetCost() + 50.00;
    }
}

// Main Class
class DecoratorPatternExample {
    static void Main(string[] args) {
        ICourse course = new BasicCourse();
        Console.WriteLine("Description: " + course.GetDescription());
        Console.WriteLine("Cost: $" + course.GetCost());

        ICourse premiumCourse = new PremiumContentDecorator(course);
        Console.WriteLine("\nDescription: " + premiumCourse.GetDescription());
        Console.WriteLine("Cost: $" + premiumCourse.GetCost());
    }
}   

Output

 Description: Basic Course
Cost: $100

Description: Basic Course with Premium Content
Cost: $150

Explanation

  • Decorator Pattern: This pattern dynamically adds new functionality (like "Premium Content") to objects without altering their structure, by wrapping the BasicCourse inside PremiumContentDecorator.
  • Course Interface & Concrete Component: ICourse defines the base functionality (GetDescription and GetCost), while BasicCourse provides the basic course implementation.
  • Decorator Classes: CourseDecorator is an abstract decorator that passes calls to the wrapped ICourse, and PremiumContentDecorator adds extra cost and description to the base course.

5. Facade Pattern Design Pattern

  • Facade Design Pattern provides a simplified interface to a complex system of classes, making it easier to interact with the system.
  • This pattern is like a front desk that handles communication with a complex backend, shielding users from the complexity.

Example

 using System;

// Subsystem Classes
class CourseRegistration {
    public void RegisterCourse(string courseName) {
        Console.WriteLine("Registering for course: " + courseName);
    }
}

class PaymentProcessing {
    public void ProcessPayment(double amount) {
        Console.WriteLine("Processing payment of $" + amount);
    }
}

class Certification {
    public void IssueCertificate(string courseName) {
        Console.WriteLine("Issuing certificate for course: " + courseName);
    }
}

// Facade
class CourseFacade {
    private CourseRegistration registration;
    private PaymentProcessing payment;
    private Certification certification;

    public CourseFacade() {
        registration = new CourseRegistration();
        payment = new PaymentProcessing();
        certification = new Certification();
    }

    public void EnrollInCourse(string courseName, double amount) {
        registration.RegisterCourse(courseName);
        payment.ProcessPayment(amount);
        certification.IssueCertificate(courseName);
    }
}

// Main Class
class FacadePatternExample {
    static void Main(string[] args) {
        CourseFacade facade = new CourseFacade();
        facade.EnrollInCourse("Java Programming", 100.00);
    }
}  

Output

 
Registering for course: Java Programming
Processing payment of $100
Issuing certificate for course: Java Programming

Explanation

  • Subsystem Classes: CourseRegistration, PaymentProcessing, and Certification are independent subsystems that handle different parts of the course enrollment process.
  • Facade Class: CourseFacade provides a simplified interface (EnrollInCourse) that coordinates actions across multiple subsystems, hiding the complexity.
  • Main Class: The FacadePatternExample demonstrates how to use the facade to enroll in a course without directly interacting with the subsystems, improving usability.

6. Flyweight Design Pattern

  • The Flyweight Design Pattern minimizes memory usage by sharing as much data as possible between similar objects.
  • This pattern is useful when dealing with a large number of similar objects, as it reduces the overhead of object creation and memory consumption.

Example

 using System;
using System.Collections.Generic;

// Flyweight Interface
interface ICourse {
    void Display(string studentName);
}

// Concrete Flyweight
class ConcreteCourse : ICourse {
    private string courseName;

    public ConcreteCourse(string courseName) {
        this.courseName = courseName;
    }

    public void Display(string studentName) {
        Console.WriteLine("Course: " + courseName + " for Student: " + studentName);
    }
}

// Flyweight Factory
class CourseFactory {
    private Dictionary<string, ICourse> courseMap = new Dictionary<string, ICourse>();

    public ICourse GetCourse(string courseName) {
        if (!courseMap.ContainsKey(courseName)) {
            courseMap[courseName] = new ConcreteCourse(courseName);
        }
        return courseMap[courseName];
    }
}

// Main Class
class FlyweightPatternExample {
    static void Main(string[] args) {
        CourseFactory factory = new CourseFactory();

        ICourse javaCourse = factory.GetCourse("Java Programming");
        javaCourse.Display("Aman");

        ICourse pythonCourse = factory.GetCourse("Python Programming");
        pythonCourse.Display("Ankita");

        ICourse javaCourse2 = factory.GetCourse("Java Programming");
        javaCourse2.Display("Raghav");
    }
}  

Output

 Course: Java Programming for Student: Aman
Course: Python Programming for Student: Ankita
Course: Java Programming for Student: Raghav

Explanation

  • Flyweight Pattern: Reduces memory usage by reusing existing objects (ConcreteCourse) for common data (like course names) rather than creating new objects.
  • Flyweight Factory: CourseFactory manages the shared instances of ConcreteCourse objects, ensuring that each course is only created once and reused.
  • Main Class: Demonstrates the pattern by retrieving and reusing the same Java Programming course object for both "Alice" and "Charlie," reducing redundant object creation.

7. Proxy Pattern Design Pattern

  • Proxy Pattern provides a placeholder or surrogate for another object to control access to it.
  • This pattern is useful for managing access to resources, adding security, or optimizing performance by delaying expensive operations until they are needed.

Example

 using System;

// Subject Interface
interface IScholarHatCourse {
    void Enroll();
}

// Real Subject
class RealCourse : IScholarHatCourse {
    private string courseName;

    public RealCourse(string courseName) {
        this.courseName = courseName;
    }

    public void Enroll() {
        Console.WriteLine("Enrolling in course: " + courseName);
    }
}

// Proxy
class CourseProxy : IScholarHatCourse {
    private RealCourse realCourse;
    private string courseName;

    public CourseProxy(string courseName) {
        this.courseName = courseName;
    }

    public void Enroll() {
        if (realCourse == null) {
            realCourse = new RealCourse(courseName);
        }
        realCourse.Enroll();
    }
}

// Main Class
class ProxyPatternExample {
    static void Main(string[] args) {
        IScholarHatCourse course = new CourseProxy("Java Programming");
        course.Enroll();  // RealCourse is created and enrolled in
    }
} 

Output

 Enrolling in course: Java Programming

Explanation

  • Proxy Pattern: Acts as an intermediary (CourseProxy) for the RealCourse, controlling access to it and allowing for deferred instantiation.
  • Lazy Initialization: The RealCourse is only created when the Enroll method is called on the CourseProxy, demonstrating efficient resource usage.
  • Separation of Concerns: The CourseProxy encapsulates the logic for managing the RealCourse, allowing for additional functionalities (like access control or logging) to be added without modifying the RealCourse directly.

3. Behavioral Design Patterns

  • Behavioral design patterns are a type of design pattern in developing software that deals with the communication and interaction of objects and classes.
  • They emphasize how objects and classes work together and communicate to complete tasks and responsibilities.

Behavioral Patterns

The following are types of behavioral patterns:

  1. Observer Pattern Design Pattern
  2. Strategy Pattern Design Pattern
  3. Command Pattern Design Pattern
  4. State Pattern Design Pattern
  5. Template Method Pattern Design Pattern
  6. Chain of Responsibility Pattern Design Pattern
  7. Mediator Pattern Design Pattern
  8. Memento Pattern Design Pattern
Let's explore one by one:

1. Observer Design Pattern

  • Observer Design Patternallows an object to notify other objects about changes in its state.
  • This pattern is useful when you need a system where changes in one part of the system automatically update other parts, like a news feed where subscribers get updates when new articles are published.

Example

 using System;
using System.Collections.Generic;

// Subject Interface
interface ICourseSubject {
    void AddObserver(ICourseObserver observer);
    void RemoveObserver(ICourseObserver observer);
    void NotifyObservers();
}

// Concrete Subject
class Course : ICourseSubject {
    private List observers = new List();
    private string courseName;

    public Course(string courseName) {
        this.courseName = courseName;
    }

    public void AddObserver(ICourseObserver observer) {
        observers.Add(observer);
    }

    public void RemoveObserver(ICourseObserver observer) {
        observers.Remove(observer);
    }

    public void NotifyObservers() {
        foreach (ICourseObserver observer in observers) {
            observer.Update(courseName);
        }
    }

    public void ChangeCourseName(string newCourseName) {
        this.courseName = newCourseName;
        NotifyObservers();
    }
}

// Observer Interface
interface ICourseObserver {
    void Update(string courseName);
}

// Concrete Observer
class Student : ICourseObserver {
    private string studentName;

    public Student(string studentName) {
        this.studentName = studentName;
    }

    public void Update(string courseName) {
        Console.WriteLine(studentName + " received update: " + courseName);
    }
}

// Main Class
class ObserverPatternExample {
    static void Main(string[] args) {
        Course course = new Course("Java Programming");

        Student student1 = new Student("Alice");
        Student student2 = new Student("Bob");

        course.AddObserver(student1);
        course.AddObserver(student2);

        course.ChangeCourseName("Advanced Java Programming");
    }
}  

Output

 Alice received update: Advanced Java Programming
Bob received update: Advanced Java Programming

Explanation

  • Observer Pattern: Implements a mechanism where Course (the subject) maintains a list of Student (observers) that are notified of changes in course details (in this case, the course name).
  • Dynamic Notification: When the course name changes via the ChangeCourseName method, all registered students receive updates automatically through the NotifyObservers method.
  • Loose Coupling: The Course and Student classes are loosely coupled, allowing for flexible communication between them without direct dependencies, making the system easier to maintain and extend.

2. Strategy Pattern Design Pattern

  • Strategy Design Patternenables selecting an algorithm at runtime without altering the code that uses it.
  • This pattern is proper when you have multiple ways to operate, such as different sorting algorithms, and you want to choose the best one dynamically.
Read More:
Data Structures Sorting: Types and Examples Explained
Sort in Python- An Easy Way to Learn

Example

 using System;

// Strategy Interface
interface IPaymentStrategy
{
    void Pay(double amount);
}

// Concrete Strategies
class CreditCardPayment : IPaymentStrategy
{
    public void Pay(double amount)
    {
        Console.WriteLine($"Paid ${amount} using Credit Card.");
    }
}

class PayPalPayment : IPaymentStrategy
{
    public void Pay(double amount)
    {
        Console.WriteLine($"Paid ${amount} using PayPal.");
    }
}

// Context
class ShoppingCart
{
    private IPaymentStrategy paymentStrategy;

    public void SetPaymentStrategy(IPaymentStrategy paymentStrategy)
    {
        this.paymentStrategy = paymentStrategy;
    }

    public void Checkout(double amount)
    {
        paymentStrategy.Pay(amount);
    }
}

// Main Class
class StrategyPatternExample
{
    static void Main(string[] args)
    {
        ShoppingCart cart = new ShoppingCart();

        cart.SetPaymentStrategy(new CreditCardPayment());
        cart.Checkout(100.00);

        cart.SetPaymentStrategy(new PayPalPayment());
        cart.Checkout(200.00);
    }
}  

Output

 Paid $100 using Credit Card.
Paid $200 using PayPal.

Explanation

  • Strategy Pattern: This design pattern defines a family of algorithms (payment methods) encapsulated in classes that can be interchangeable, allowing for dynamic selection at runtime.
  • Payment Strategies: Two concrete implementations of the IPaymentStrategy interface are provided: CreditCardPayment and PayPalPayment, each implementing the Pay method to handle payments differently.
  • Context Usage: The ShoppingCart class acts as the context that uses the payment strategies. The SetPaymentStrategy method allows changing the payment method dynamically, and Checkout processes the payment using the selected strategy.

3. Command Design Pattern

  • Command Design Patternturns requests or simple operations into objects, allowing them to be executed, undone, or queued.
  • This pattern is handy for implementing action history or undo mechanisms, like in a text editor where you can undo previous actions.

Example

 using System;

// Command Interface
interface ICommand
{
    void Execute();
}

// Concrete Command
class EnrollInCourseCommand : ICommand
{
    private string courseName;

    public EnrollInCourseCommand(string courseName)
    {
        this.courseName = courseName;
    }

    public void Execute()
    {
        Console.WriteLine($"Enrolled in course: {courseName}");
    }
}

// Invoker
class CourseRegistration
{
    private ICommand command;

    public void SetCommand(ICommand command)
    {
        this.command = command;
    }

    public void PerformAction()
    {
        command.Execute();
    }
}

// Main Class
class CommandPatternExample
{
    static void Main(string[] args)
    {
        ICommand enrollCommand = new EnrollInCourseCommand("Java Programming");
        CourseRegistration registration = new CourseRegistration();

        registration.SetCommand(enrollCommand);
        registration.PerformAction();
    }
} 

Output

 Enrolled in course: Java Programming

Explanation

  • Command Pattern: This design pattern encapsulates a request as an object, allowing parameterization of clients with different requests and support for undoable operations.
  • Command Implementation: The EnrollInCourseCommand class implements the ICommand interface and defines the action of enrolling in a course, which is executed when the Execute method is called.
  • Invoker Functionality: The CourseRegistration class serves as the invoker, holding a command instance and invoking its Execute method when the action is performed, allowing for flexible command execution.

4. StateDesign Pattern

  • State Design Pattern allows an object to change its behavior when its internal state changes.
  • This pattern is useful for managing state-dependent behavior, such as a vending machine that changes its operations based on whether it's idle, accepting coins, or dispensing items.

Example

 using System;

// State Interface
interface ICourseState
{
    void Handle();
}

// Concrete States
class NotStartedState : ICourseState
{
    public void Handle()
    {
        Console.WriteLine("Course not started yet.");
    }
}

class InProgressState : ICourseState
{
    public void Handle()
    {
        Console.WriteLine("Course is in progress.");
    }
}

class CompletedState : ICourseState
{
    public void Handle()
    {
        Console.WriteLine("Course is completed.");
    }
}

// Context
class Course
{
    private ICourseState state;

    public void SetState(ICourseState state)
    {
        this.state = state;
    }

    public void Request()
    {
        state.Handle();
    }
}

// Main Class
class StatePatternExample
{
    static void Main(string[] args)
    {
        Course course = new Course();

        course.SetState(new NotStartedState());
        course.Request();

        course.SetState(new InProgressState());
        course.Request();

        course.SetState(new CompletedState());
        course.Request();
    }
}   

Output

 Course not started yet.
Course is in progress.
Course is completed.

Explanation

  • State Pattern: The state pattern allows an object to alter its behavior when its internal state changes, providing a way to handle state transitions cleanly.
  • Concrete States: The NotStartedState, InProgressState, and CompletedState classes implement the ICourseState interface, defining specific behaviors for each course state.
  • Context (Course Class): The Course class holds a reference to the current state, and by calling the Request method, it delegates the behavior to the current state object, making it easy to switch between states dynamically.

5. Template Method Design Pattern

  • Template Method Design Pattern defines the skeleton of an algorithm in a base class but lets subclasses override specific steps.
  • This pattern ensures a consistent structure while allowing customization, like a cooking recipe where the steps are fixed, but the ingredients can vary.

Example

 using System;

// Abstract Class
abstract class CourseTemplate
{
    public void Enroll() // Removed final as it's not a C# keyword
    {
        SelectCourse();
        MakePayment();
        SendConfirmation();
    }

    protected abstract void SelectCourse();
    protected abstract void MakePayment();

    protected void SendConfirmation()
    {
        Console.WriteLine("Confirmation sent.");
    }
}

// Concrete Class 1
class JavaCourse : CourseTemplate
{
    protected override void SelectCourse()
    {
        Console.WriteLine("Java Programming course selected.");
    }

    protected override void MakePayment()
    {
        Console.WriteLine("Payment made for Java course.");
    }
}

// Concrete Class 2
class PythonCourse : CourseTemplate
{
    protected override void SelectCourse()
    {
        Console.WriteLine("Python Programming course selected.");
    }

    protected override void MakePayment()
    {
        Console.WriteLine("Payment made for Python course.");
    }
}

// Main Class
class TemplateMethodPatternExample
{
    static void Main(string[] args)
    {
        CourseTemplate javaCourse = new JavaCourse();
        javaCourse.Enroll();

        CourseTemplate pythonCourse = new PythonCourse();
        pythonCourse.Enroll();
    }
} 

Output

 Java Programming course selected.
Payment made for Java course.
Confirmation sent.
Python Programming course selected.
Payment made for Python course.
Confirmation sent. 

Explanation

  • Template Method Pattern: Defines the algorithm's skeleton in CourseTemplate, allowing subclasses to customize specific steps while maintaining overall structure.
  • Abstract Methods: Subclasses (JavaCourse and PythonCourse) implement the abstract methods SelectCourse and MakePayment to provide specific behaviors.
  • Final Method: The Enroll method in CourseTemplate ensures a consistent sequence of operations across all course types (selecting a course, making a payment, and sending confirmation).

6. Chain of Responsibility Design Pattern

  • Chain of Responsibility Design Pattern passes a request along a chain of handlers until one of them handles it.
  • This pattern is useful for processing requests in a sequence, such as filtering and handling user inputs or processing various levels of a request in a web application.

Example

 using System;

// Handler Interface
interface ICourseHandler
{
    void SetNextHandler(ICourseHandler handler);
    void HandleRequest(string request);
}

// Concrete Handlers
class EnrollmentHandler : ICourseHandler
{
    private ICourseHandler nextHandler;

    public void SetNextHandler(ICourseHandler handler)
    {
        nextHandler = handler;
    }

    public void HandleRequest(string request)
    {
        if (request.Equals("Enroll"))
        {
            Console.WriteLine("Handling enrollment.");
        }
        else if (nextHandler != null)
        {
            nextHandler.HandleRequest(request);
        }
    }
}

class PaymentHandler : ICourseHandler
{
    private ICourseHandler nextHandler;

    public void SetNextHandler(ICourseHandler handler)
    {
        nextHandler = handler;
    }

    public void HandleRequest(string request)
    {
        if (request.Equals("Payment"))
        {
            Console.WriteLine("Handling payment.");
        }
        else if (nextHandler != null)
        {
            nextHandler.HandleRequest(request);
        }
    }
}

class CertificationHandler : ICourseHandler
{
    private ICourseHandler nextHandler;

    public void SetNextHandler(ICourseHandler handler)
    {
        nextHandler = handler;
    }

    public void HandleRequest(string request)
    {
        if (request.Equals("Certificate"))
        {
            Console.WriteLine("Handling certification.");
        }
        else if (nextHandler != null)
        {
            nextHandler.HandleRequest(request);
        }
    }
}

// Main Class
class ChainOfResponsibilityPatternExample
{
    static void Main(string[] args)
    {
        ICourseHandler enrollment = new EnrollmentHandler();
        ICourseHandler payment = new PaymentHandler();
        ICourseHandler certification = new CertificationHandler();

        enrollment.SetNextHandler(payment);
        payment.SetNextHandler(certification);

        enrollment.HandleRequest("Payment");
        enrollment.HandleRequest("Certificate");
    }
}   

Output

 Handling payment.
Handling certification.

Explanation

  • Handler Interface: The ICourseHandler interface defines methods for setting the next handler and handling requests, establishing a structure for the concrete handler classes.
  • Concrete Handlers: Classes (EnrollmentHandler, PaymentHandler, CertificationHandler) implement the ICourseHandler interface, handling specific requests (e.g., "Enroll", "Payment", "Certificate") and passing unhandled requests to the next handler in the chain.
  • Chain of Responsibility: In the Main method, a chain of handlers is created. Each handler processes requests in sequence, allowing flexible and dynamic request handling without coupling request senders to specific handlers.

7. Mediator Design Pattern

  • Mediator Design Patterncentralizes communication between objects, reducing the dependency between them.
  • This pattern is useful for managing complex interactions in a system, such as coordinating the components of a chat application to handle user messages and notifications.

Example

 using System;
using System.Collections.Generic;

// Mediator Interface
interface ICourseMediator
{
    void RegisterStudent(Student student);
    void NotifyStudents(string message);
}

// Concrete Mediator
class Course : ICourseMediator
{
    private List students = new List();

    public void RegisterStudent(Student student)
    {
        students.Add(student);
    }

    public void NotifyStudents(string message)
    {
        foreach (var student in students)
        {
            student.Update(message);
        }
    }
}

// Colleague
class Student
{
    private string name;
    private ICourseMediator mediator;

    public Student(string name, ICourseMediator mediator)
    {
        this.name = name;
        this.mediator = mediator;
        mediator.RegisterStudent(this);
    }

    public void Update(string message)
    {
        Console.WriteLine($"{name} received message: {message}");
    }
}

// Main Class
class MediatorPatternExample
{
    static void Main(string[] args)
    {
        Course course = new Course();

        Student student1 = new Student("Alice", course);
        Student student2 = new Student("Bob", course);

        course.NotifyStudents("Course registration opens tomorrow.");
    }
}  

Output

 Alice received message: Course registration opens tomorrow.
Bob received message: Course registration opens tomorrow.

Explanation

  • Mediator Interface: ICourseMediator defines methods for registering students and notifying them, promoting loose coupling between the mediator and students.
  • Concrete Mediator: The Course class implements ICourseMediator, managing a list of students and handling notifications, ensuring each student receives relevant messages.
  • Colleague Class: The Student class registers itself with the mediator and implements an Update method for message handling, facilitating centralized communication through the Course mediator.

8. Memento Design Pattern

  • TheMemento Design Patterncaptures and restores an object's state without exposing its internal structure.
  • This pattern is helpful in saving and retrieving an object's state, like implementing an undo feature in a text editor where you can revert to a previous version of the document.

Example

 using System;

// Memento
class CourseMemento
{
    private string courseName;

    public CourseMemento(string courseName)
    {
        this.courseName = courseName;
    }

    public string GetCourseName()
    {
        return courseName;
    }
}

// Originator
class Course
{
    private string courseName;

    public void SetCourseName(string courseName)
    {
        this.courseName = courseName;
    }

    public CourseMemento Save()
    {
        return new CourseMemento(courseName);
    }

    public void Restore(CourseMemento memento)
    {
        this.courseName = memento.GetCourseName();
    }

    public override string ToString()
    {
        return "Course name: " + courseName;
    }
}

// Caretaker
class CourseCaretaker
{
    private CourseMemento memento;

    public void SaveMemento(CourseMemento memento)
    {
        this.memento = memento;
    }

    public CourseMemento GetMemento()
    {
        return memento;
    }
}

// Main Class
class MementoPatternExample
{
    static void Main(string[] args)
    {
        Course course = new Course();
        CourseCaretaker caretaker = new CourseCaretaker();

        course.SetCourseName("Java Programming");
        Console.WriteLine(course);

        caretaker.SaveMemento(course.Save());

        course.SetCourseName("Advanced Java Programming");
        Console.WriteLine(course);

        course.Restore(caretaker.GetMemento());
        Console.WriteLine(course);
    }
}   

Output

 Course name: Java Programming
Course name: Advanced Java Programming
Course name: Java Programming

Explanation

  • Memento Class: CourseMemento stores the state of the Course (its name) and provides a method to retrieve that state, allowing for state restoration.
  • Originator Class: Course represents the object whose state can be saved and restored. It has methods to set its name, save its current state as a CourseMemento, and restore its state from a memento.
  • Caretaker Class: CourseCaretaker manages the memento, storing it and providing access to it when needed. This class encapsulates the memento to protect the state of the Course from direct access, maintaining the integrity of the object's state.

When to Use Design Patterns?

  • Reoccurring Problems: Use design patterns when you meet reoccurring design issues with well-defined answers. Design patterns provide tried-and-true approaches to typical program design difficulties.
  • Flexibility and Reusability: Use design patterns to encourage code reuse, flexibility, and maintenance. They assist in structuring code so that it is easier to adapt and extend as requirements change.
  • Design Principles: Use design patterns to implement core design principles including dividing up concerns, encapsulation, and dependency inversion. They aid in improving modularity and decreasing dependency between components.
  • Communication: Design patterns can help team members communicate more effectively. Design patterns provide a shared vocabulary and understanding of how to tackle certain challenges, which aids cooperation and code comprehension.
  • Performance: Design patterns can sometimes increase performance by minimizing resource utilization, lowering overhead, or increasing code execution efficiency.

How to Choose the Right Design Pattern?

Choosing a design pattern depends on the problem that you are solving and the requirements for solving that problem; consider the following:

  • Problem Type: Match the pattern to the nature of the problem, such as creational, structural, or behavioral issues.
  • Flexibility: Assess if the pattern allows flexibility and scalability during future changes.
  • Complexity: Assure the complexity of the pattern doesn't complexify your design but simplifies it.
  • Common Solutions: Observe well-used patterns in similar circumstances and leverage solutions proven to work.

Choosing the right pattern will increase the maintainability and effectiveness of your design.

Best Practices for Using Design Patterns

Best practices for using design patterns:

  • Understand the Purpose: Ensure you understand the pattern's purpose and its applicability in order to avoid misapplication.
  • Keep It Simple:Use patterns only if they really simplify a problem and, therefore, never introduce extraneous complexity.
  • Follow Conventions: Stick to existing naming conventions and patterns, as this will give the code much more readability.
  • Use of Document: Clearly document why and how the pattern is used so that it is useful to future developers.
Read more:
.Net Design Patterns Interview Questions, You Must Know!
Most Frequently Asked Software Architect Interview Questions and Answers
Summary

This tutorial presents an in-depth overview of design patterns in C#, including definitions and implementations. It also includes creational, structural, and behavioral patterns. It begins with an overview of each category, followed by extensive explanations and sample code for patterns including Singleton, Factory Method, Adapter, and Observer. This tutorial shows when to utilize design patterns, how to select the proper one, and the best practices for successful implementation. Also, consider our Software Architecture and Design Certification Training for a better understanding of other Java concepts.

FAQs

Design patterns in C# are reusable solutions to common software design problems. They provide proven development paradigms to address issues related to object creation, object interaction, and system structure, making code more maintainable, flexible, and scalable. 

  • Command: Encapsulates a request as an object, allowing for parameterization of clients with different requests, queuing requests, and logging them.
  • Chain of Responsibility: Passes a request along a chain of handlers until one handles the request, allowing multiple objects to handle a request without knowing who will process it.

The Facade pattern provides a simplified interface to a complex subsystem. It's used to reduce the complexity of interactions between subsystems by hiding intricate details behind a straightforward interface. 

 You should use design patterns when:
  • You encounter a common design issue or need a proven solution.
  • You want to improve code maintainability and readability.
  • Your code needs to be scalable and flexible for future changes.

The difference between structural and behavioral patterns are:
  • Structural Patterns: Focus on how objects and classes are composed to form larger structures (e.g., Adapter, Composite, Decorator).
  • Behavioral Patterns: Focus on how objects interact and communicate with each other (e.g., Observer, Strategy, Command).

Share Article
About Author
Shailendra Chauhan (Microsoft MVP, Founder & CEO at Scholarhat by DotNetTricks)

Shailendra Chauhan is the Founder and CEO at ScholarHat by DotNetTricks which is a brand when it comes to e-Learning. He provides training and consultation over an array of technologies like Cloud, .NET, Angular, React, Node, Microservices, Containers and Mobile Apps development. He has been awarded Microsoft MVP 9th time in a row (2016-2024). He has changed many lives with his writings and unique training programs. He has a number of most sought-after books to his name which has helped job aspirants in cracking tough interviews with ease.
Accept cookies & close this