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,
- Creational
- Structural
- 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,
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- 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.