Structural Design Patterns In C#

Structural Design Patterns In C#

If you are new, I highly recommend you to read my first article which is about Creational Design Patterns In C#. As we explained in an earlier post, we have three main categories in Design Patterns,

  1. Creational
  2. Structural
  3. Behavioral

Structural Design Patterns help you to put types and objects and other stuff together to create a bigger struct but at the same time, it keeps high flexibility and optimum performance for you. There are seven patterns that exists in this family,

  1. Adapter
  2. Bridge
  3. Composite
  4. Decorator
  5. Facade
  6. Flyweight
  7. Proxy

Adapter Design Pattern

As its name implies, the adapter pattern adjusts communication between two alien types and make them understand each other.

public class AlienType {
    public string SayJupiterianHi() {
        return "Hello Man from Earth, I am from JUPITER";
    }
}
public interface IAdapter {
    string SayHi();
}
public class Adapter: IAdapter {
    private readonly AlienType _alienType;
    public Adapter(AlienType alienType) {
        _alienType = alienType;
    }
    public string SayHi() {
        return _alienType.SayJupiterianHi();
    }
}
public static class AdapterExample {
    public static void Test() {
        var adapter = new Adapter(new AlienType());
        Console.WriteLine(adapter.SayHi());
    }
    //result:
    //Hello Man from Earth, I am from JUPITER
}

Bridge Design Pattern

This pattern separates interface or abstraction from its implementation, so for example you may be using two different technologies in two implementations.

public interface IBridge {
    void PrintDetails();
}
public class BridgeA: IBridge {
    public void PrintDetails() {
        Console.WriteLine("data across Bridge A");
    }
}
public class BridgeB: IBridge {
    public void PrintDetails() {
        Console.WriteLine("data across Bridge B");
    }
}
public interface IBridgeUser {
    public IBridge Bridge {
        get;
        set;
    }
    void PrintDetails();
}
public class BridgeUser: IBridgeUser {
    public IBridge Bridge {
        get;
        set;
    }
    public void PrintDetails() {
        Bridge.PrintDetails();
    }
}
public static class BridgeExample {
    public static void Test() {
        var bridgeUser = new BridgeUser();
        bridgeUser.Bridge = new BridgeA();
        bridgeUser.PrintDetails();
        bridgeUser.Bridge = new BridgeB();
        bridgeUser.PrintDetails();
    }
    //result:
    //data across Bridge A
    //data across Bridge B
}

Composite Design Pattern

In simple words, we put objects in one bigger object with a tree structure so we can present hierarchies, and also this pattern let you treat uniformly with a single object or composite object.

public abstract class Animal {
    protected string Name;
    protected Animal(string name) {
        Name = name;
    }
    public virtual void Add(Animal animal) {
        throw new NotImplementedException();
    }
    public virtual void PrintTree(int indent) {
        throw new NotImplementedException();
    }
}
public class Species: Animal {
    public Species(string name): base(name) {}
    public override void PrintTree(int indent) {
        Console.WriteLine(new String('-', indent) + " " + Name);
    }
}
public class SpeciesComposite: Animal {
    private readonly List < Animal > _animals = new List < Animal > ();
    public SpeciesComposite(string name): base(name) {}
    public override void Add(Animal animal) {
        _animals.Add(animal);
    }
    public override void PrintTree(int indent) {
        Console.WriteLine(new String('-', indent) + "+ " + Name);
        // Display each child element on this node
        foreach(var animal in _animals) {
            animal.PrintTree(indent + 2);
        }
    }
}
public static class CompositeExample {
    public static void Test() {
        var mammal = new SpeciesComposite("Mammal");
        var carnivore = new SpeciesComposite("Carnivore");
        var nonCarnivore = new SpeciesComposite("Non-Carnivore");
        var tiger = new Species("Tiger");
        var leopard = new Species("Leopard");
        var sheep = new Species("Sheep");
        var cow = new Species("Cow");
        carnivore.Add(tiger);
        carnivore.Add(leopard);
        nonCarnivore.Add(sheep);
        nonCarnivore.Add(cow);
        mammal.Add(carnivore);
        mammal.Add(nonCarnivore);
        mammal.PrintTree(1);
    }
    //result:
    //-+ Mammal
    //---+ Carnivore
    //----- Tiger
    //----- Leopard
    //---+ Non-Carnivore
    //----- Sheep
    //----- Cow
}

Decorator Design Pattern

This pattern helps you to extend your object responsibilities dynamically with less subclassing.

public interface ICar {
    public string Tire {
        get;
    }
    public string Engine {
        get;
    }
    public abstract void ShowParts();
}
public class NormalCar: ICar {
    public string Tire => "Pirelli";
    public string Engine => "v4";
    public void ShowParts() {
        Console.WriteLine(this.ToJson());
    }
}
public class SportCarDecorator: ICar {
    private readonly ICar _car;
    public SportCarDecorator(ICar car) {
        _car = car;
    }
    public string Tire => _car.Tire;
    public string Engine => _car.Engine;
    public string Nitro => "NOS";
    public void ShowParts() {
        Console.WriteLine(this.ToJson());
    }
}
public static class DecoratorExample {
    public static void Test() {
        var normalCar = new NormalCar();
        normalCar.ShowParts();
        var decoratedCar = new SportCarDecorator(normalCar);
        decoratedCar.ShowParts();
    }
    //result:
    //{"Tire":"Pirelli","Engine":"v4"}
    //{"Tire":"Pirelli","Engine":"v4","Nitro":"NOS"}
}

Facade Design Pattern

This pattern gathers all interfaces or complex systems of classes or frameworks or etc, and makes using them simple for you.

public class Facade {
    private readonly SubSys1 _subSys1;
    private readonly SubSys2 _subSys2;
    public Facade(SubSys1 subSys1, SubSys2 subSys2) {
        _subSys1 = subSys1;
        _subSys2 = subSys2;
    }
    public void Action() {
        _subSys1.Action();
        _subSys2.Action();
    }
}
public class SubSys1 {
    public void Action() {
        Console.WriteLine("i am action by subsys1");
    }
}
public class SubSys2 {
    public void Action() {
        Console.WriteLine("i am action by subsys2");
    }
}
public static class FacadeExample {
    public static void Test() {
        var subsys1 = new SubSys1();
        var subsys2 = new SubSys2();
        var facade = new Facade(subsys1, subsys2);
        facade.Action();
    }
    //result:
    //i am action by subsys1
    //i am action by subsys2
}

Flyweight Design Pattern

Mainly target of using this pattern is to save your memory by using shared objects.

public class Flyweight {
    private readonly List < KeyValuePair < string, Heavy >> _sharedObjects = new();
    public Flyweight() {
        _sharedObjects.Add(new KeyValuePair < string, Heavy > ("A", new Heavy()));
        _sharedObjects.Add(new KeyValuePair < string, Heavy > ("B", new Heavy()));
        _sharedObjects.Add(new KeyValuePair < string, Heavy > ("C", new Heavy()));
    }
    public Heavy GetObject(string key) {
        return _sharedObjects.SingleOrDefault(c => c.Key == key).Value;
    }
}
public interface IHeavy {
    public void Operation(string name);
}
public class Heavy: IHeavy {
    public void Operation(string name) {
        Console.WriteLine(name);
    }
}
public static class FlyweightExample {
    public static void Test() {
        var flyweight = new Flyweight();
        flyweight.GetObject("A").Operation("Hey");
        flyweight.GetObject("B").Operation("We Are Using");
        flyweight.GetObject("C").Operation("Shared Memory");
        var heavy = new Heavy();
        heavy.Operation("But I am bad and use my own memory");
    }
    //result:
    //Hey
    //We Are Using
    //Shared Memory
    //But I am bad and use my own memory
}

Proxy Design Pattern

This pattern uses a middle type to control access to main type.

public abstract class MainAbstraction {
    public abstract void Call();
}
public class Main: MainAbstraction {
    public override void Call() {
        Console.WriteLine("calling by Main class");
    }
}
public class Proxy: MainAbstraction {
    private Main _main;
    public Proxy(Main main) {
        _main = main;
    }
    public override void Call() {
        _main ?? = new Main();
        Console.WriteLine("proxy do some work before calling");
        _main.Call();
    }
}
public static class ProxyExample {
    public static void Test() {
        var proxy = new Proxy(new Main());
        proxy.Call();
    }
    //result:
    //proxy do some work before calling
    //calling by Main class
}

That's it. Congratulation! now you have been familiar with the basic patterns of Structural Design. I will explain the last main category in two different articles because Behavioral Design includes many patterns and it is better to split it into two articles.


Similar Articles