![Search icon](https://hackernoon.imgix.net/search-new.png?w=19&h=19)
Unlock the secrets of software architecture with Mastering Software Architecture: 11 Key Design Patterns Explained.
2. Design Pattern — Adapter
3. Design Pattern — Builder
4. How to use the Chain of Responsibility Pattern
5. Design Pattern — Decorator
6. Design Pattern — Factory Method
7. Design Pattern — Iterator
8. Design Pattern — Mediator
9. Design Pattern — Observer
10. Advance Property Pattern C# 8.0
11. Design Pattern — Singleton
According to Gang of Four, abstract factory patterns can be assumed as the factory for creating factories.
Abstract factory pattern is purely an extension factory method; it’s recommended to go through the factory method before understanding abstract factory design.
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;
}
}
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}}
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
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.
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.
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.
According to Gang of Four, a creational pattern “Builder” allows one to separate and reuse a specific method to build something.
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; }
}
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
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.
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 ModelSedan : CarBuilder
{
public override void SetIsSUV()
{
_car.IsSUV = false;
}
public override void SetName()
{
_car.Name = "Maruti Sedan";
}
public override void SetSpeed()
{
_car.TopSpeed = 2000;
}
}
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.
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.
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;}
}
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);
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.
According to Gang of Four, the pattern adds extra responsibilities to a class object dynamically.
Let’s consider the example of buying a car worth ten lakhs; the company provides the following additional features.
With some additional features, the total price of the car increases. Let’s implement the above use case using the Decorator Pattern.
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.
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";
}
Override the methods as
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";
}
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);
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());
}
Congratulations..!! You have successfully implemented the use case using the decorator pattern.
According to the Gang of Four, the factory method allows the subclass to determine which class object should be created.
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.
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.
Saving account balance: 10000 Rs
Current account balance: 20000 Rs
According to Gang of Four, the iterator pattern provides a process to obtain the aggregator object without knowing its implementation.
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.
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.
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.
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.
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);
}
}
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);
}
}
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());
}
}
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.
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.
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?'
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);
}
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();
}
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?'
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.
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.
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);
}
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.”
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 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);
}
}
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();
}
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 take an example of Toll Calculator and see how pattern matching helps to write an algorithm for that.
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
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 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)}");
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.
Refer to pattern-matching syntax with single & multiple property classes. Link
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,
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 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)}");
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.”
Gang of Four — Singleton design pattern ensures that a particular class has only one instance/object and a global access point.
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;
}
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));
}
The console output returns true; congratulations. You have successfully implemented the Singleton Pattern.
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()
{
}
}
https://github.com/ssukhpinder/DesignPatterns
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 😛.
Thank you for being a part of the C# community!