DEV Community

Cover image for Structural Design Patterns In C#
Bardia Mostafavi
Bardia Mostafavi

Posted on

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 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 object and other stuff together to create bigger struct but at same time, its keep high flexibility and optimum performance for you. there are seven patterns exists in this family :

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

Adapter

as its name implies , 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
    }
Enter fullscreen mode Exit fullscreen mode

Bridge

this pattern separate interface or abstraction from its implementation, so for example you may have 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
    }
Enter fullscreen mode Exit fullscreen mode

Composite

in simple word , we put objects in one bigger object with tree structure so we can present hierarchie, and also this pattern let you to treat uniformly with 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
    }
Enter fullscreen mode Exit fullscreen mode

Decorator

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"}
    }
Enter fullscreen mode Exit fullscreen mode

Facade

This pattern gathers all interfaces or complex systems of classes or frameworks or etc , and make using of them simplify 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
    }
Enter fullscreen mode Exit fullscreen mode

Flyweight

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
    }
Enter fullscreen mode Exit fullscreen mode

Proxy

This pattern use 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
    }
Enter fullscreen mode Exit fullscreen mode

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 the Behavioral Design includes many patterns and it is better to split it into two articles.

Discussion (0)