Skip to content

JustinRidings/CSharpAlgorithms

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

C# Algorithms

A collection of Design Patterns and Algorithms, implemented in C#.

Prereqs

You will need .NET 8 Runtime/SDK in order to code / build. Additionally, it helps to use an IDE. I recommend Visual Studio.

OR use WinGet

winget install dotnet-runtime-8
winget install dotnet-sdk-8
winget install Microsoft.VisualStudio.2022.Community.Preview

Project Layout

The Patterns directory contains sub-directories for the different types of design patterns that exist, align with implementations. The WhiteboardSolutions.cs file contains static implementations for various whiteboarding questions. You can Demo each pattern or whiteboard solution by running the Program to see the output.

Behavioral Patterns

  • Iterator
    • Summary: Provides a way to access elements of a collection sequentially without exposing its underlying representation.
    • When to use: When you need to traverse a collection without exposing its internal structure.
graph TD
A[Collection] -->|createIterator| B[Iterator]
B -->|first| C[Element1]
B -->|next| D[Element2]
B -->|hasNext| E[Element3]
B -->|currentItem| F[Element4]
Loading
  • Observer
    • Summary: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
    • When to use: When changes to one object need to be propagated to multiple dependent objects automatically.
graph TD
A[Subject] -->|registerObserver| B[Observer1]
A -->|removeObserver| C[Observer2]
A -->|notifyObservers| D[Observer3]
B -->|update| E[Observer1State]
C -->|update| F[Observer2State]
D -->|update| G[Observer3State]
Loading
  • Strategy
    • Summary: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. The strategy pattern lets the algorithm vary independently from the clients that use it.
    • When to use: When you have multiple algorithms for a specific task and want to switch between them dynamically.
graph TD
A[Context] -->|setStrategy| B[StrategyInterface]
B -->|executeStrategy| C[ConcreteStrategyA]
B -->|executeStrategy| D[ConcreteStrategyB]
A -->|performTask| E[Result]
Loading
  • Command
    • Summary: Encapsulates a request as an object, thereby allowing for parameterization of clients with queues, requests, and operations.
    • When to use: When you need to parameterize objects with operations, delay execution, or support undoable operations.
graph TD
A[Invoker] -->|executeCommand| B[Command]
B -->|execute| C[ConcreteCommand]
C -->|execute| D[Receiver]
D -->|action| E[Result]
Loading
  • State
    • Summary: Allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
    • When to use: When an object's behavior depends on its state, and it needs to change behavior at runtime depending on its current state.
graph TD
A[Context] -->|setState| B[State]
B -->|handleRequest| C[ConcreteStateA]
B -->|handleRequest| D[ConcreteStateB]
A -->|request| E[Result]
Loading
  • Mediator
    • Summary: Defines an object that encapsulates how a set of objects interact. This pattern promotes loose coupling by keeping objects from referring to each other explicitly and allows their interaction to be varied independently.
    • When to use: When you need to reduce the complexity of communication between multiple objects and centralize the interaction logic.
graph TD
A[Mediator] -->|notify| B[Colleague1]
A -->|notify| C[Colleague2]
B -->|send| A
C -->|send| A
Loading
  • Chain of Responsibility
    • Summary: Allows a request to pass through a chain of handlers. Each handler either processes the request or passes it to the next handler in the chain.
    • When to use: When multiple objects can handle a request, and you want to avoid coupling the sender to a specific receiver.
graph TD
A[Client] -->|sendRequest| B[Handler1]
B -->|handleRequest| C[Handler2]
C -->|handleRequest| D[Handler3]
D -->|handleRequest| E[Result]
Loading
  • Visitor
    • Summary: When you need to perform various operations on objects without changing their classes.
    • When to use: When you want to separate an algorithm from the objects on which it operates.
graph TD
    A[Client] -->|creates| B[ConcreteElement]
    B -->|accept| C[Visitor]
    C -->|calls visit on| D[ConcreteVisitor]
    D -->|operates on| B
    B -->|notifies| A

    subgraph ConcreteElement
        E[Candy]
        F[Fruit]
    end

    subgraph Visitor
        G[ShoppingCartVisitor]
        H[ReportVisitor]
    end

    E -->|accept| G
    E -->|accept| H
    F -->|accept| G
    F -->|accept| H
Loading
  • Template Method
    • Summary: Defines the skeleton of an algorithm in a method, allowing subclasses to redefine certain steps without changing the algorithm's structure.
    • When to use: When you need to define the general flow of an algorithm but allow subclasses to customize specific steps.
graph TD
    A[Client] -->|calls| B[TemplateMethod]
    B -->|invokes| C[OpenDocument]
    B -->|invokes| D[ExtractContent]
    B -->|invokes| E[FormatDocument]
    B -->|invokes| F[SaveDocument]

    classDef abstract fill:#f9f,stroke:#333,stroke-width:4px;
    class B abstract;

    subgraph ConcreteClasses
        G[WordDocumentProcessor]
        H[PdfDocumentProcessor]
    end

    C -->|implemented by| G
    D -->|implemented by| G
    E -->|implemented by| G
    F -->|implemented by| G

    C -->|implemented by| H
    D -->|implemented by| H
    E -->|implemented by| H
    F -->|implemented by| H
Loading
  • Memento
    • Summary: Captures and externalizes an object's internal state without violating encapsulation, so that the object can be restored to this state later.
    • When to use: When you need to implement a feature that allows an object to be restored to a previous state, such as undo operations in text editors or maintaining historical states.
graph TD
    A[Client] -->|calls| B[TextEditor]
    B -->|creates/restores| C[TextMemento]
    D[TextHistory] -->|stores| C
    D -->|retrieves| C
Loading

Creational Patterns

  • Builder
    • Summary: Separates the construction of a complex object from its representation so that the same construction process can create different representations.
    • When to use: When you need to construct complex objects step by step and allow for different representations.
graph TD
    A[Director] -->|construct| B[BuilderInterface]
    B -->|buildPart| C[ConcreteBuilder]
    C -->|getResult| D[Product]
Loading
  • Factory
    • Summary: Defines an interface for creating an object, but lets subclasses alter the type of objects that will be created.
    • When to use: When you need to delegate the instantiation logic to subclasses or to centralize the creation logic for a specific type of object.
graph TD
A[Client] -->|create| B[Factory]
B -->|createProduct| C[ProductInterface]
C -->|ConcreteProductA| D[ProductA]
C -->|ConcreteProductB| E[ProductB]
Loading
  • Singleton
    • Summary: Ensures a class has only one instance and provides a global point of access to it.
    • When to use: When you need exactly one instance of a class to control access to shared resources.
graph TD
    A[Client1] -->|getInstance| B[Singleton]
    A[Client2] -->|getInstance| B[Singleton]
    B -->|method| C[Result]
Loading
  • LazyLoading
    • Summary: Delays the initialization of an object until it is actually needed, which can improve performance and reduce memory usage.
    • When to use: When you want to defer the initialization of an object until it's really needed to save resources.
graph TD
  A[Client] -->|request| B[Proxy]
  B -->|createInstance| C[RealSubject]
  C -->|operation| D[Result]
Loading
  • Prototype
    • Summary: Allows you to copy existing objects without making your code dependent on their classes.
    • When to use: When creating new instances of a class is expensive or complicated, or when you want to replicate an object configuration with minor changes.
graph TD
  A[Client] -->|clone| B[Prototype]
  B -->|clone| C[ConcretePrototype1]
  B -->|clone| D[ConcretePrototype2]
  C -->|use| E[Prototype1Result]
  D -->|use| F[Prototype2Result]
Loading
  • Abstract Factory
    • Summary: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
    • When to use: When a system needs to be independent of how its objects are created, composed, and represented, or when a system should be configured with one of multiple families of products.
graph TD
  A[Client] -->|use| B[NotificationService]
  B -->|create| C[INotificationFactory]
  C -->|create| D[BasicNotificationFactory]
  C -->|create| E[PremiumNotificationFactory]
  D -->|create| F[BasicEmailNotification]
  D -->|create| G[BasicSmsNotification]
  E -->|create| H[PremiumEmailNotification]
  E -->|create| I[PremiumSmsNotification]
  F -->|send| J[Basic Email Sent]
  G -->|send| K[Basic SMS Sent]
  H -->|send| L[Premium Email Sent]
  I -->|send| M[Premium SMS Sent]
Loading

Structural Patterns

  • Facade
    • Summary: Provides a unified interface to a set of interfaces in a subsystem, making the subsystem easier to use.
    • When to use: When you need to simplify interactions with a complex system by providing a unified interface.
graph TD
    A[Client] -->|use| B[Facade]
    B -->|subsystem1| C[SubsystemClass1]
    B -->|subsystem2| D[SubsystemClass2]
    C -->|operation| E[Result1]
    D -->|operation| F[Result2]
Loading
  • Adapter
    • Summary: Converts the interface of a class into another interface the clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
    • When to use: When you need to integrate a new class that doesn't fit the existing interface or legacy system.
graph TD
    A[Client] -->|use| B[Adapter]
    B -->|adapt| C[Adaptee]
    C -->|specificRequest| D[Request]
Loading
  • Decorator
    • Summary: Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
    • When to use: When you need to add behaviors or responsibilities to objects dynamically without modifying their code.
graph TD
    A[Client] -->|use| B[Component]
    B -->|wraps| C[ConcreteComponent]
    B -->|decorates| D[ConcreteDecorator]
    D -->|decorates| E[ConcreteComponent]
    E -->|operation| F[Result]
Loading
  • Composite
    • Summary: Composes objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
    • When to use: When you need to work with tree structures and treat individual objects and compositions uniformly.
graph TD
    A[Client] -->|use| B[Component]
    B -->|composite| C[Composite]
    B -->|leaf| D[Leaf]
    C -->|add| D
    C -->|operation| E[Result]
    D -->|operation| E
Loading
  • Flyweight
    • Summary: Minimizes memory usage by sharing as much data as possible with similar objects.
    • When to use: When an application uses a large number of objects that share common properties, and you want to reduce memory consumption and improve performance by sharing common data.
graph TD
    A[Client] -->|requestTree| B[TreeFactory]
    B -->|getTreeType| C[TreeType]
    C -->|shared data| D[Tree]
    D -->|unique data| E[Draw]
    A -->|position| E
Loading
  • Proxy
    • Summary: Provides a surrogate or placeholder for another object to control access to it.
    • When to use: When you need to control access to an object, add additional functionality such as logging or authentication, or work with remote objects or objects that are expensive to create.
graph TD
    A[Client] -->|request| B[Proxy]
    B -->|authenticate| C[RealSubject]
    B -->|log request| D[Logger]
    C -->|execute| E[Result]
    D -->|store log| F[LogStorage]
    A -->|get result| E
Loading
  • Bridge
    • Summary: Decouples an abstraction from its implementation, allowing them to vary independently. Useful for avoiding a permanent binding between an abstraction and its implementation.
    • When to use: When you need to separate the abstraction of a concept from its actual implementation, allowing both to be developed and extended independently.
graph TD
    A[Client] -->|calls| B[Shape]
    B -->|uses| C[IColor]
    B -->|uses| D[Circle]
    B -->|uses| E[Rectangle]
    C -->|implements| F[RedColor]
    C -->|implements| G[BlueColor]
    D -->|uses| H[RedColor]
    D -->|uses| I[BlueColor]
    E -->|uses| J[RedColor]
    E -->|uses| K[BlueColor]
    F -->|operation| L[ApplyColor]
    G -->|operation| M[ApplyColor]
    H -->|operation| N[Draw]
    I -->|operation| O[Draw]
    J -->|operation| P[Draw]
    K -->|operation| Q[Draw]
Loading

About

A collection of C# Design Patterns and algorithms

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages