Exploring Structural Patterns

Tutorial 3 of 5

1. Introduction

Goal of the Tutorial

This tutorial aims to introduce you to the concepts of Structural Patterns in C#, specifically focusing on the Adapter and Decorator patterns.

What You Will Learn

By the end of this tutorial, you will be able to:

  • Understand the concept of Structural Patterns
  • Implement the Adapter pattern in C#
  • Implement the Decorator pattern in C#

Prerequisites

Basic understanding of C# programming and Object-Oriented Programming (OOP) concepts is required.

2. Step-by-Step Guide

Structural Patterns

Structural Patterns in programming provide a manner to define relationships between classes or objects so that they can form larger structures. They simplify the structure and promote reusability.

Adapter Pattern

The Adapter pattern is a structural design pattern that allows objects with incompatible interfaces to work together. It wraps itself around an object and exposes a standard interface to interact with that object.

Here's an example of an Adapter pattern:

// the 'Adaptee'
public class OldSystem
{
    public string OldRequest()
    {
        return "Old System";
    }
}

// the 'Adapter'
public class Adapter : INewSystem
{
    private OldSystem _oldSystem;

    public Adapter(OldSystem oldSystem)
    {
        _oldSystem = oldSystem;
    }

    public string Request()
    {
        return $"Adapter: ({_oldSystem.OldRequest()})";
    }
}

// the 'Target'
public interface INewSystem
{
    string Request();
}

In the above example, Adapter is making the OldSystem compatible with INewSystem.

Decorator Pattern

The Decorator pattern provides a way to add new behavior to an object dynamically, without altering its implementation.

Here's an example of a Decorator pattern:

// 'Component'
public interface IComponent
{
    string Operation();
}

// 'ConcreteComponent'
public class Component : IComponent
{
    public string Operation()
    {
        return "ConcreteComponent";
    }
}

// 'Decorator'
public class Decorator : IComponent
{
    protected IComponent _component;

    public Decorator(IComponent component)
    {
        _component = component;
    }

    public virtual string Operation()
    {
        return _component.Operation();
    }
}

// 'ConcreteDecoratorA'
public class ConcreteDecoratorA : Decorator
{
    public ConcreteDecoratorA(IComponent comp) : base(comp) { }

    public override string Operation()
    {
        return $"ConcreteDecoratorA({base.Operation()})";
    }
}

In the above example, ConcreteDecoratorA is adding additional behavior to Component.

3. Code Examples

Adapter Pattern Example

class Program
{
    static void Main(string[] args)
    {
        OldSystem oldSystem = new OldSystem();
        INewSystem adapter = new Adapter(oldSystem);
        Console.WriteLine(adapter.Request());  // Output: Adapter: (Old System)
    }
}

Decorator Pattern Example

class Program
{
    static void Main(string[] args)
    {
        IComponent component = new Component();
        Console.WriteLine("Without decorator: ");
        Console.WriteLine(component.Operation());  // Output: ConcreteComponent

        Console.WriteLine("\nWith decorator: ");
        IComponent decorator = new ConcreteDecoratorA(component);
        Console.WriteLine(decorator.Operation());  // Output: ConcreteDecoratorA(ConcreteComponent)
    }
}

4. Summary

In this tutorial, we have learned about the Adapter and Decorator Structural Patterns. We also implemented these patterns in C#. Remember, the Adapter pattern is used to make two incompatible interfaces work together while the Decorator pattern allows adding new behavior to an object dynamically.

5. Practice Exercises

  1. Implement the Adapter pattern to make a Rectangle and Circle class work together.
  2. Implement the Decorator pattern to add a new 'draw' behavior to the Rectangle class.

Remember, the best way to learn is by doing. Try to modify and play with the code examples given above. Happy coding!