Hello friends,
In our previous article, we talked about the kinds of mistakes we make while trying to learn design patterns and and the techniques that can be applied to make them stick better. In this article, we're continuing from where we left off. Our goal is to be able to explain how to build a design pattern in a sentence or two. This way, with an explanation stripped of details, we'll also make them stick better in our minds.
In the next article, we'll touch on the points where design patterns have structural and purposeful things in common or where they differ from each other. Since we'll touch on the relationships of each design pattern we examine with other patterns, being able to give short answers to at least the questions of what each one's purpose is and how it's written will make our job a lot easier.
Now let's continue by adding to the point where we left off in the previous article.
Creational Design Patterns
They're patterns that create objects instead of instantiating objects directly. This gives the program more flexibility in deciding which objects need to be created for a particular situation.
- Singleton:
Purpose: Restricts object creation for a class to one instance. To put it a bit differently, it ensures that a single object is used by all the objects that need it.
Example: For example, like everyone in the house using the fridge at home, or a group of neighbors on the street sharing a garbage container. When we need this kind of object while coding, the design pattern that will guarantee it is singleton.
How It's Done: in the first condition, for all requests that need the object, it's checked whether the object has been created. If there's no instance of the object yet, a lock statement is written on an object, and a second condition is placed inside this statement against the possibility of more than one request getting in, and if the instance still hasn't been created, it's created. As the requests that finish their work inside the lock exit the lock statement, every incoming request will see that the object has been created, so the object won't be created again.
Key Code Block
private static readonly object _lock = new object(); public static Singleton GetInstance(string value) { if (_instance == null) { lock (_lock) { if (_instance == null) { _instance = new Singleton(); } } } return _instance; } - Factory Method:
Purpose: Used to create instances from different classes derived from the same interface. It's a single method.
Example: For example, let's think of a factory that produces different models of the same car brand.
How Is It Done?: All the objects to be created are derived from the same interface. Then the related object is created with a method inside a factory object that is also derived from an interface.
Key Code Block
public interface IGuiFactory { public IComponent CreateButton(string color); public IComponent CreateTextBox(string color); public IComponent CreateTable(string color); } public class GuiFactory:IGuiFactory { public IComponent CreateButton(string color) { var button = new Button(color); button.Draw(); return button; } public IComponent CreateTextBox(string color) { var textBox = new TextBox(color); textBox.Draw(); return textBox; } public IComponent CreateTable(string color) { var table = new Table(color); table.Draw(); return table; } } - Abstract Factory:
Purpose: Creates a group of related but different objects. It's a class, made up of multiple factory methods. It can even sometimes be made up of more than one factory class.
Example: For example, let's think of a factory where different models of different car brands are produced. You could even add a third dimension, for example, using different factories to produce different models of different brands for different countries.
How Is It Done?: All the objects to be created are derived from the same interface. Then the related objects are created with multiple methods inside a factory object that is also derived from an interface. If you want to produce more than one factory, more factory objects can be produced too.
Key Code Block
public interface IUniversityFactory { University CreateUniversity(int id, string name); Course CreateCourse(int id, string name); Student CreateStudent(int id, string name, int age); Prof CreateProf(int id, string name, int age, Course course); } public interface IUniversityRemoteFactory { UniversityRemote CreateUniversityRemote(int id, string name); CourseRemote CreateCourseRemote(int id, string name); Student CreateStudent(int id, string name, int age); ProfRemote CreateProfRemote(int id, string name, int age, CourseRemote course); } public class Program { public static void Main() { var universityFactory=new UniversityFactory(); var university = universityFactory.CreateUniversity(1, "University 1"); var course = universityFactory.CreateCourse(1, "Course 1"); var prof = universityFactory.CreateProf(1,"Ahmet",45,course); var student = universityFactory.CreateStudent(1,"Mehmet",45); university.AddStudent(student); university.AddCourse(course); university.AddProf(prof); var universityRemoteFactory = new UniversityRemoteFactory(); var universityRemote = universityRemoteFactory.CreateUniversityRemote(1, "UniversityRemote 1"); var courseRemote = universityRemoteFactory.CreateCourseRemote(1, "CourseRemote 1"); var profRemote = universityRemoteFactory.CreateProfRemote(1,"Ahmet Remote",45,courseRemote); var studentRemote = universityFactory.CreateStudent(1,"Mehmet Remote",45); universityRemote.AddStudent(studentRemote); universityRemote.AddCourse(courseRemote); universityRemote.AddProf(profRemote); } } - Builder:
Purpose: By separating the creation process/steps of a complex object from its representation, it allows different types of the same object to be created.
Example: When we want to buy a car, we go to the dealership. We say which features we want, and after a certain time the car we want is produced at the factory and delivered. Naturally, the production process is complex; many tasks are completed in order, following certain processes. While the building of the car and its processes are done at the factory, its presentation and sale are done by the dealership.
How Is It Done?: In its simplest form, with one or more builder objects, the parts of the related objects are written so that they're created in different functions. Then, with a director object, one or more builder objects are passed as parameters to the function that will create the related object.
Key Code Block
An example of creating a single object
public interface IBuilder { void BuildPartA(); void BuildPartB(); void BuildPartC(); } public class Director { private IBuilder _builder; public Director(IBuilder builder) { _builder = builder } public IBuilder BuildFullFeaturedProduct() { this._builder.BuildPartA(); this._builder.BuildPartB(); this._builder.BuildPartC(); return _builder; } } class Program { static void Main(string[] args) { IBuilder builder = new ConcreteBuilder(); var director = new Director(builder); var concreteBuilder = director.BuildFullFeaturedProduct(); } } - Prototype:
Purpose: Allows cloning/copying objects that are very costly to create from similar instances created before.
Example: When we go to a locksmith to copy a key, let's imagine for a moment that the locksmith asks for the lock cylinder of the door the key will be copied for, opens it up, measures the lengths millimeter by millimeter, calculates the angles of the key's teeth and produces the key for us according to this calculation. Since we now know about copying, this sounds so unnecessary, doesn't it?
If we think about how much cpu,memory and network the system uses to produce some objects we easily create with a single line of code, we can understand how often we make this mistake. Since we're not the ones writing that code, it may feel as if there isn't that much work involved. But in a multi-user application where performance is important, an object being created by using the system and the network heavily can cause serious problems.
How Is It Done?: A copy function is defined inside the object to be copied, and a copy of the object is returned.
Key Code Block
public class Person { public int Age; public DateTime BirthDate; public string Name; public IdInfo IdInfo; public Person ShallowCopy() { return (Person) this.MemberwiseClone(); } public Person DeepCopy() { Person clone = (Person) this.MemberwiseClone(); clone.IdInfo = new IdInfo(IdInfo.IdNumber); clone.Name = String.Copy(Name); return clone; } Structural Design Patterns
- Adapter:
Purpose: Allows two different interfaces to work together.
Example: The adapters we use in real life do exactly the same job. The adapters we use to make the plug of an electronic device we bought from America fit the sockets in Turkey are an example of this.
How Is It Done?: Like the adapters we use in real life, an intermediate object is created, and the source object is passed to the adapter object as a parameter. The data the client expects is presented by transforming it from the received source object.
Key Code Block
public interface ITarget { string GetRequest(); } class Adaptee { public string GetSpecificRequest() { return "Specific request."; } } class Adapter : ITarget { private readonly Adaptee _adaptee; public Adapter(Adaptee adaptee) { this._adaptee = adaptee; } public string GetRequest() { return $"This is '{this._adaptee.GetSpecificRequest()}'"; } } class Program { static void Main(string[] args) { Adaptee adaptee = new Adaptee(); ITarget target = new Adapter(adaptee); Console.WriteLine(target.GetRequest()); } } - Bridge:
Purpose: Used to separate an object's abstraction from its implementation. This way, the two can be extended independently of each other.
Example: It's a bit hard to give a real-life example, but again, let's say no analogy is perfect and try to give an example. For example, let's say we're producing cars. It would be impossible for us to produce the cars in a way that meets the wishes of all customers. Instead, it would be less costly to produce the cars according to the technical documents we have (abstraction) with all the parts they basically need to have, and to get the features that vary from customer to customer from our customers during the purchase and complete the production. For example, the color, whether it has a sunroof or not.
How Is It Done?: It consists of two parts, abstraction and implementation. Part of the value the client requests is the part that doesn't change (abstraction), and the other part is the part that can be changed, that is, chosen on request (implementation). For example, we can write code that draws a shape, but we may need to let the client choose the color. For this, the abstraction part is written first, and the changing part is passed to the abstraction object as a parameter.
Key Code Block
public interface IBridge { void Function1(); void Function2(); } public interface IAbstractBridge { void CallMethod1(); void CallMethod2(); } public class AbstractBridge : IAbstractBridge { public IBridge bridge; public AbstractBridge(IBridge bridge) { this.bridge = bridge; } public void CallMethod1() { this.bridge.Function1(); } public void CallMethod2() { this.bridge.Function2(); } } - Composite:
Purpose: If a parent-child (tree) relationship can be established between objects that have a common interface, it's used to abstract the client from the parts and let it deal with a single object.
Example: The goal is to create objects in a tree structure as a single composite object. Of course, libraries like this now exist in languages, but if we think about the dates the book was written, trying to explain it as a pattern seems to make sense. For example, you went to a dealership, asked the price of a car, and the salesperson started calculating the prices of all the parts one by one for you. Naturally, this would be very pointless. Of course, to calculate the price of a car, the prices of all the parts have to be known one by one, but as the buyer, this doesn't concern us.
How Is It Done?: The same interface is applied to all the elements in the hierarchical structure. Each element keeps the list of its child elements or the information of a single child element.
Key Code Block:
public interface IComponent { void DisplayPrice(); } public class Leaf : IComponent { public int Price { get; set; } public string Name { get; set; } public Leaf(string name, int price) { this.Price = price; this.Name = name; } public void DisplayPrice() { Console.WriteLine(Name +" : "+ Price); } } public class Composite : IComponent { public string Name { get; set; } List<IComponent> components = new List<IComponent>(); public Composite(string name) { this.Name = name; } public void AddComponent(IComponent component) { components.Add(component); } public void DisplayPrice() { Console.WriteLine(Name); foreach (var item in components) { item.DisplayPrice(); } } } static void Main(string[] args) { IComponent hardDisk = new Leaf("Hard Disk", 2000); IComponent ram = new Leaf("RAM", 3000); IComponent cpu = new Leaf("CPU", 2000); IComponent mouse = new Leaf("Mouse", 2000); IComponent keyboard = new Leaf("Keyboard", 2000); Composite motherBoard = new Composite("Peripherals"); Composite cabinet = new Composite("Cabinet"); Composite peripherals = new Composite("Peripherals"); Composite computer = new Composite("Computer"); motherBoard.AddComponent(cpu); motherBoard.AddComponent(ram); cabinet.AddComponent(motherBoard); cabinet.AddComponent(hardDisk); peripherals.AddComponent(mouse); peripherals.AddComponent(keyboard); computer.AddComponent(cabinet); computer.AddComponent(peripherals); computer.DisplayPrice(); Console.WriteLine(); keyboard.DisplayPrice(); Console.WriteLine(); cabinet.DisplayPrice(); Console.Read(); } - Decorator:
Purpose: Used to dynamically add new responsibilities to an object or change them.
Example: It may not be a perfect example, but I think it will be memorable. For example, having LPG installed in our gasoline car could be an example of this.
How Is It Done?: The object itself is passed as a parameter to the decorator class, which implements the same interface as the object to be decorated.
Key Code Block:
public interface IPizza { string MakePizza(); } public class PlainPizza : IPizza { public string MakePizza() { return "Plain Pizza"; } } public abstract class PizzaDecorator : IPizza { protected Pizza pizza; public PizzaDecorator(Pizza pizza) { this.pizza = pizza; } public virtual string MakePizza() { return pizza.MakePizza(); } } public class ChickenPizzaDecorator : PizzaDecorator { public ChickenPizzaDecorator(Pizza pizza) : base(pizza) { } public override string MakePizza() { return pizza.MakePizza() + AddChicken(); } private string AddChicken() { return ", Chicken added"; } } public class VegPizzaDecorator : PizzaDecorator { public VegPizzaDecorator(Pizza pizza) : base(pizza) { } public override string MakePizza() { return pizza.MakePizza() + AddVegetables(); } private string AddVegetables() { return ", Vegetables added"; } } class Program { static void Main(string[] args) { PlainPizza plainPizzaObj = new PlainPizza(); string plainPizza = plainPizzaObj.MakePizza(); Console.WriteLineSource:https://dev.to/muratcabuk/how-to-write-design-patterns-design-patterns-2-5k3 Related posts

Before You Solve Another Interview Question, Try This 45 Minute Mock
Seven Neurons Is All It Takes to Backdoor Your AI Agent
Building a no-ad-gate catalogue of 4,000+ osu! skins
