Understanding Structural Design Patterns

Structural patterns deal with the arrangement of classes and objects to form larger structures, optimizing how components interact with each other. Common Structural patterns include:

  • Adapter Pattern: Allows incompatible interfaces to work together, providing a bridge between two incompatible interfaces.
  • Composite Pattern: Composes objects into tree-like structures, treating individual objects and groups uniformly.
  • Decorator Pattern: Adds behavior to individual objects without affecting other objects of the same class, providing a flexible alternative to subclassing.

Adapter Pattern in C#

The Adapter pattern is used to make incompatible interfaces compatible, allowing two classes to work together. This pattern is commonly used when integrating external systems or legacy code that doesn’t match the interface of the new codebase:


public interface ITarget
{
    void Request();
}

public class Adaptee
{
    public void SpecificRequest()
    {
        Console.WriteLine("Called SpecificRequest");
    }
}

public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }

    public void Request()
    {
        _adaptee.SpecificRequest();
    }
}

// Usage
ITarget target = new Adapter(new Adaptee());
target.Request();

In this example, `Adapter` makes `Adaptee` compatible with `ITarget` by implementing `Request` as a bridge to `SpecificRequest`. This allows code dependent on `ITarget` to work seamlessly with `Adaptee` without modifying it directly.

Composite Pattern in C#

The Composite pattern organizes objects into tree structures, treating individual objects and compositions uniformly. This is especially useful for representing hierarchical data or grouping components:


public abstract class Component
{
    protected string Name;

    protected Component(string name)
    {
        Name = name;
    }

    public abstract void Display(int depth);
}

public class Leaf : Component
{
    public Leaf(string name) : base(name) { }

    public override void Display(int depth)
    {
        Console.WriteLine(new string('-', depth) + Name);
    }
}

public class Composite : Component
{
    private readonly List _children = new();

    public Composite(string name) : base(name) { }

    public void Add(Component component)
    {
        _children.Add(component);
    }

    public override void Display(int depth)
    {
        Console.WriteLine(new string('-', depth) + Name);
        foreach (var component in _children)
        {
            component.Display(depth + 2);
        }
    }
}

// Usage
Composite root = new Composite("Root");
root.Add(new Leaf("Leaf A"));
root.Add(new Leaf("Leaf B"));

Composite comp = new Composite("Composite X");
comp.Add(new Leaf("Leaf XA"));
comp.Add(new Leaf("Leaf XB"));

root.Add(comp);
root.Display(1);

This pattern treats `Leaf` and `Composite` elements in the same way, allowing nested structures to be displayed recursively. The result is a clean and intuitive approach to handling complex hierarchies.

Decorator Pattern in C#

The Decorator pattern dynamically adds behavior to objects without altering their class. This pattern is useful when individual objects need different functionality from other instances of the same class:


public interface INotifier
{
    void Send(string message);
}

public class Notifier : INotifier
{
    public void Send(string message)
    {
        Console.WriteLine("Sending notification: " + message);
    }
}

public class FacebookDecorator : INotifier
{
    private readonly INotifier _notifier;

    public FacebookDecorator(INotifier notifier)
    {
        _notifier = notifier;
    }

    public void Send(string message)
    {
        _notifier.Send(message);
        Console.WriteLine("Also posting to Facebook: " + message);
    }
}

public class TwitterDecorator : INotifier
{
    private readonly INotifier _notifier;

    public TwitterDecorator(INotifier notifier)
    {
        _notifier = notifier;
    }

    public void Send(string message)
    {
        _notifier.Send(message);
        Console.WriteLine("Also tweeting: " + message);
    }
}

// Usage
INotifier notifier = new FacebookDecorator(new TwitterDecorator(new Notifier()));
notifier.Send("Hello World!");

In this example, each decorator (Facebook and Twitter) adds functionality to the `Notifier` without modifying its original behavior. This allows you to combine decorators to create flexible and customizable functionality.

Structural Patterns in Front-End Development with React

Structural patterns are also valuable in front-end frameworks like React. The Decorator pattern, for example, is commonly applied using Higher-Order Components (HOCs) to extend component functionality:


import React from 'react';

interface Props {
    message: string;
}

const BaseComponent: React.FC = ({ message }) => (
    
{message}

);

const withBorder = (Component: React.FC) => (props: Props) => (
    
 

);

const EnhancedComponent = withBorder(BaseComponent);

// Usage
;

In this example, `withBorder` is a Higher-Order Component (HOC) that wraps `BaseComponent`, adding a border around it. This pattern allows us to add new behaviors to components without modifying the original component directly.

Conclusion

Structural design patterns like Adapter, Composite, and Decorator provide practical solutions for organizing and extending components in complex systems. By using these patterns in C# or React, developers can handle object relationships more effectively, creating scalable and modular architectures. Structural patterns simplify complex interactions between components, making code easier to manage, extend, and adapt.