Community for developers to learn, share their programming knowledge. Register!
Design Patterns in C#

Structural Design Patterns in C#


In this article, we will delve into the world of Structural Design Patterns using C#. If you're interested in enhancing your programming skills and understanding how to create more efficient and maintainable code, this article serves as a perfect training resource for you. Structural design patterns are essential in software development, allowing developers to compose classes and objects into larger structures while keeping them flexible and efficient.

What are Structural Patterns?

Structural patterns are design patterns that deal with the composition of classes or objects. They help ensure that if one part of a system changes, the entire system doesn’t need to do the same. These patterns focus on how objects and classes can be combined to form larger structures while maintaining flexibility. In C#, structural patterns can be particularly beneficial in managing complex systems, as they promote code reuse and reduce dependencies.

Common characteristics of structural patterns include:

  • Composition over inheritance: They often favor composing objects rather than relying heavily on class inheritance.
  • Encapsulation: They encapsulate parts of the system, providing a clear interface for interaction.
  • Flexibility: They allow for easy changes and extensions to the system without impacting existing code.

Key Structural Patterns in C#

The most recognized structural patterns in C# include:

  • Adapter Pattern
  • Decorator Pattern
  • Facade Pattern
  • Composite Pattern

Let’s explore each of these patterns in more detail.

Adapter Pattern: Bridging Interfaces

The Adapter Pattern allows incompatible interfaces to work together. This pattern acts as a bridge between two incompatible interfaces. In C#, it’s commonly used to integrate classes with different interfaces into a single, unified interface.

Example

Consider a scenario where you have a Bird interface and a Duck class that implements it. You also have a Turkey class that doesn't implement the Bird interface but has a similar method. The adapter will allow the Turkey to be used where a Bird is expected.

public interface IBird
{
    void Quack();
}

public class Duck : IBird
{
    public void Quack()
    {
        Console.WriteLine("Quack!");
    }
}

public class Turkey
{
    public void Gobble()
    {
        Console.WriteLine("Gobble!");
    }
}

public class TurkeyAdapter : IBird
{
    private readonly Turkey _turkey;

    public TurkeyAdapter(Turkey turkey)
    {
        _turkey = turkey;
    }

    public void Quack()
    {
        _turkey.Gobble();
    }
}

In this example, the TurkeyAdapter allows a Turkey to be treated as a Bird, demonstrating how the adapter pattern bridges interfaces.

Decorator Pattern: Enhancing Object Functionality

The Decorator Pattern allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class. This pattern is a great way to extend functionalities in a flexible manner.

Example

Consider a simple coffee ordering system. You can have a basic coffee class, and you might want to add different condiments to it dynamically.

public interface ICoffee
{
    string GetDescription();
    double Cost();
}

public class BasicCoffee : ICoffee
{
    public string GetDescription() => "Basic Coffee";
    
    public double Cost() => 2.00;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;

    public CoffeeDecorator(ICoffee coffee)
    {
        _coffee = coffee;
    }

    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual double Cost() => _coffee.Cost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) {}

    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override double Cost() => _coffee.Cost() + 0.50;
}

With this setup, you can dynamically add milk or any other condiments to your coffee, enhancing the functionality without modifying existing classes.

Facade Pattern: Simplifying Complex Systems

The Facade Pattern provides a simplified interface to a complex subsystem. It defines a higher-level interface that makes the subsystem easier to use.

Example

Imagine a home theater system with multiple components like a DVD player, projector, and sound system. Instead of interacting with each component separately, a facade can be created.

public class HomeTheaterFacade
{
    private readonly DVDPlayer _dvdPlayer;
    private readonly Projector _projector;
    private readonly SoundSystem _soundSystem;

    public HomeTheaterFacade(DVDPlayer dvdPlayer, Projector projector, SoundSystem soundSystem)
    {
        _dvdPlayer = dvdPlayer;
        _projector = projector;
        _soundSystem = soundSystem;
    }

    public void WatchMovie(string movie)
    {
        _dvdPlayer.On();
        _projector.On();
        _soundSystem.On();
        _dvdPlayer.Play(movie);
    }
}

This facade allows the user to interact with the home theater system through a single interface, resulting in a cleaner and more manageable codebase.

Composite Pattern: Working with Tree Structures

The Composite Pattern is used when you need to treat individual objects and compositions of objects uniformly. This pattern is particularly useful for hierarchies or tree structures.

Example

Consider a graphic drawing application where you have both shapes and groups of shapes. You can treat both shapes and groups uniformly.

public interface IGraphic
{
    void Draw();
}

public class Circle : IGraphic
{
    public void Draw() => Console.WriteLine("Drawing a Circle");
}

public class CompositeGraphic : IGraphic
{
    private readonly List<IGraphic> _graphics = new List<IGraphic>();

    public void Add(IGraphic graphic) => _graphics.Add(graphic);
    
    public void Draw()
    {
        foreach (var graphic in _graphics)
        {
            graphic.Draw();
        }
    }
}

Here, both Circle and CompositeGraphic can be treated as IGraphic, allowing for flexibility in handling complex structures.

Benefits of Structural Patterns in Design

Using structural patterns in your C# applications provides several advantages:

  • Improved Code Reusability: By composing objects, you can reuse existing code more effectively.
  • Increased Flexibility: They allow for easier modifications and extensions to existing systems.
  • Clearer Code Organization: These patterns encourage a clear separation of concerns, making your codebase easier to navigate and understand.
  • Better Maintenance: With reduced dependencies and increased modularity, maintaining and updating code becomes less cumbersome.

Summary

In conclusion, structural design patterns are invaluable tools for intermediate and professional developers seeking to create robust and maintainable C# applications.

Last Update: 18 Jan, 2025

Topics:
C#
C#