22
Dec.Net Design Patterns Interview Questions, You Must Know!
What are SOLID principles?
Solid principles are a set of design patterns that aim to improve the structure of software by making it more understandable, maintainable, and extensible. The solid principles were first proposed by Robert C. Martin in his paper "Design Principles and Design Patterns". The solid principles are:
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
What is the Single Responsibility Principle (SRP)?
This principle states that a class should have one and only one responsibility. There should not be more than one reason for a class to change. SRP makes the classes compact and neat where each one is responsible for a single problem, task, or concern. For example,
Explanation- In the above example, each class is responsible for handling one responsibility.
public class Membership { public void Add() { try { //TO DO: } catch (Exception ex) { //File.WriteAllText(@"c:\Error.txt", ex.Message); var logger = new FileLogger(); logger.LogError(ex.Message); } } } public class FileLogger // SRP: Created a new class for error logging { public void LogError(string error) {
File.WriteAllText(@"c:\Error.txt", error); } }
What is the Open/Closed Principle (OCP)?
This principle states that a class should be open for extension but closed for modification.
The "closed" part of the rule states that once a class has been developed and tested, the code should only be changed to correct bugs.
The "open" part says that you should be able to extend an existing class to introduce new functionalities. In this way, you need to test only the newly created class. For example,
Explanation - A BankAccount base class contains all basic payment-related properties and methods. This class can be extended by different Account classes to achieve their functionalities. Hence it is open for extension but closed for modification.
public class Membership { //public int MembershipType { get; set; } public virtual int GetTraining() { return 2; //if (MembershipType == 1) //for Plus // return 5; //else if (MembershipType == 2) //for Pro // return 10; //else // return 2; //TO DO } } //OCP: new classes are created by inheritance public class PlusMembership: Membership { public override int GetTraining() public override int GetTraining() { return 10; } }
What is Liskov Substitution Principle (LSP)?
This principle states that derived classes must be able to substitute any object for their base classes.
In simple language, objects of a parent class can be replaced with objects of its derived classes without breaking the code. To apply this principle, the behavior of your classes becomes more important than its structure.
Example - Assume that you have an inheritance hierarchy for membership plans. Wherever you can use Membership, you should also be able to use a Plus Membership and Pro Membership, because both are subclasses of Membership.
public interface ITraining { int GetTraining(); } public interface IMembership : ITraining { void Add(); } public class Membership : IMembership { public virtual int GetTraining() { return 2; } public virtual void Add() { // TO DO: } } public class PlusMembership : Membership { public override int GetTraining() { return 5; } } public class ProMembership: Membership { public override int GetTraining() { return 10; } } // LISKOV ITraining and IMembership interface created public class TrialMembership: ITraining { public int GetTraining() { return 2; } } //public class TrialMembership: Membership //{ // public override void Add() // { // throw new NotImplementedException("Trial Membership Can't be added"); // } // public override int GetTraining() // { // return 2; // } //} class Program { } }
What is Interface Segregation Principle (ISP)?
This principle states that clients of your class should not be forced to depend on methods they do not use.
Similar to the SRP, the goal of the ISP is to reduce the side effects and frequency of required changes by splitting the code into multiple, independent parts.
Example - The service interface that is exposed to the client should contain only client-related methods not all.
public interface ITraining { int GetTraining(); //int GetLiveTraining(); //problem } public interface IMembership: ITraining { void Add(); } public interface ILiveTraining: IMembership { int GetLiveTraining(); } public class Membership: IMembership, ILiveTraining { public virtual int GetTraining() { return 2; } public int GetLiveTraining() { return 5; } public virtual void Add() { // TO DO: } } public class PlusMembership: Membershipp { public override int GetTraining() { return 5; } } public class ProMembership: Membership { public override int GetTraining() { return 10; } } // LISKOV ITraining and IMembership interface created public class TrialMembership: ITraining { public int GetTraining() { return 2; } } class Program { static void Main(string[] args) { Console.WriteLine("ISP Principle!"); //old clients with self-paced training IMembership membership = new Membership(); membership.Add(); //new clients with live training + self-paced ILiveTraining membershipLive = new Membership(); membershipLive.Add(); membershipLive.GetLiveTraining(); } }
What is the Dependency Inversion Principle (DIP)?
This principle states that high-level modules (concrete classes) should not depend on low-level modules (concrete classes). Instead, they should depend on abstract classes or interfaces.
This principle splits the dependency between the high-level and low-level modules by introducing abstraction between them.
In other words, the high-level module depends on the abstraction and the low-level module also depends on the same abstraction.
The Dependency Injection pattern is an implementation of this principle
public class Membership { private ILogger logger; public Membership(ILogger _logger) { //TO DO: read from app.config //int config = 1; //if(config==1) //{ // logger = new FileLogger(); //} //else //{ // logger = new ConsoleLogger(); //} logger = _logger; } public void Add() { try { //TO DO: } catch (Exception ex) { logger.LogError(ex.Message); } } } public interface ILogger { void LogError(string error); } public class FileLogger : ILogger { public void LogError(string error) { File.WriteAllText(@"c:\Error.txt", error); } } public class ConsoleLogger : ILogger { public void LogError(string error) { Console.WriteLine($"Error: {error}"); } } class Program { static void Main(string[] args) { Console.WriteLine("DIP Principle!"); FileLogger fileLogger = new FileLogger(); Membership member1 = new Membership(fileLogger); ConsoleLogger consoleLogger = new ConsoleLogger(); Membership member2 = new Membership(consoleLogger); } }
What is the Factory Method pattern?
In a Factory pattern, we create an object without exposing the creation logic. In this pattern, an interface is used for creating an object but lets the subclass decide which class to instantiate. The creation of an object is done when it is required. The Factory method allows a class later instantiation to subclasses.
The classes, interfaces, and objects in the above UML class diagram are defined as follows:
- Product - This is an interface for creating the objects.
- ConcreteProduct - This is a class that implements the Product interface.
- Creator - This is an abstract class and declares the factory method, which returns an object of type Product.
- ConcreteCreator - This is a class that implements the Creator class and overrides the factory method to return an instance of a ConcreteProduct.
C# Implementation Code
interface Product { //To DO: } class ConcreteProductA : Product { //To DO: } class ConcreteProductB : Product { //To DO: } abstract class Creator { public abstract Product FactoryMethod(string type); } class ConcreteCreator : Creator { public override Product FactoryMethod(string type) { switch (type) { case "A": return new ConcreteProductA(); case "B": return new ConcreteProductB(); default: throw new ArgumentException("Invalid type", "type"); } } }
Explain the Factory method pattern with real-life examples?
The examples of Factory methods are given below:
- Need to create different logger types like:
- Console Logger
- Database Logger
- File Logger etc.
- Need to create different report types like:
- Word etc.
Let’s take the following example where you can create a logger bypassing logger type.
C# Implementation Code
public class LoggerFactory { public static ILogger CreateLogger(LoggerType type) { switch (type) { case LoggerType.File: return new FileLogger(); case LoggerType.Console: default: return new ConsoleLogger(); } } } public enum LoggerType { Console, File } public interface ILogger { void LogError(string error); } public class ConsoleLogger : ILogger { public void LogError(string error) { Console.WriteLine($"Console Log: {error}"); } } public class FileLogger : ILogger { public void LogError(string error) { File.WriteAllText(@"c:\Error.txt", error); } } public class Client { ILogger _logger; public Client(ILogger logger) { _logger = logger; } public void Add() { try { // TO DO: } catch (Exception ex) { _logger.LogError(ex.Message); } } } class Program { static void Main(string[] args) { ILogger logger = LoggerFactory.CreateLogger(LoggerType.Console); Client client = new Client(logger); client.Add(); } }
When to use the Factory method pattern?
The factory pattern is useful in the following cases:
- Subclasses figure out what objects should be created.
- Parent class allows later instantiation to subclasses means the creation of an object is done when it is required.
- The process of object creation is required to centralize within the application.
- A class (creator) will not know what classes it will be required to create.
What is an Abstract Factory pattern?
Abstract Factory patterns act as a super-factory that creates other factories. This pattern is also called a Factory of factories. In the Abstract Factory pattern, an interface is responsible for creating a factory of related objects, or dependent objects without specifying their concrete classes.
For example, need to support multiple database types
- SQL Server
- Oracle
- MySQL etc.
Explain the Abstract Factory pattern with a real-life example?
Let’s take the following example where you need to create a database factory to handle database connection with SQL Server and MySQL Database.
C# - Implementation Code
public abstract class DbFactory { public abstract DbConnection GetConnection(); public abstract DbCommand GetCommand(); } public class SqlServerDbFactory : DbFactory { private SqlConnection _sqlConnection; public override DbCommand GetCommand() { SqlCommand command = new SqlCommand(); command.Connection = _sqlConnection; return command; } public override DbConnection GetConnection() { string strCon = @"data source=Shailendra\SqlExpress; initial catalog=MyDB;persist security info=True;user id=sa;password=dotnettricks;"; _sqlConnection = new SqlConnection(strCon); return _sqlConnection; } } public class MySqlDbFactory : DbFactory { private MySqlConnection _mySqlConnection; public override DbCommand GetCommand() { MySqlCommand command = new MySqlCommand(); command.Connection = _mySqlConnection; return command; } public override DbConnection GetConnection() { string strCon = @"server=localhost;user id=root;password=root;database=MyDB"; _mySqlConnection = new MySqlConnection(strCon); return _mySqlConnection; } } public class Client { private DbFactory _dbFactory; public Client(DbFactory dbFactory) { _dbFactory = dbFactory; } public void Add() { // TO DO: var connection = _dbFactory.GetConnection(); var command = _dbFactory.GetCommand(); //command.CommandText = "Select * from Users"; //connection.Open(); //var reader = command.ExecuteReader(); //reader.Close(); //connection.Close(); } } class Program { static void Main(string[] args) { SqlServerDbFactory sqlFactory = new SqlServerDbFactory(); Client client = new Client(sqlFactory); client.Add(); } }
When to use the Abstract Factory pattern?
The abstract factory pattern is helpful in the following cases:
- Create a set of related objects or dependent objects which must be used together.
- System should be configured to work with multiple families of products.
- The creation of objects should be independent from the utilizing system.
- Concrete classes should be decoupled from clients.
What other design patterns can be used with the Abstract Factory pattern?
- Internally, Abstract Factory uses Factory design pattern for creating objects. But it can also use Builder design pattern and prototype design pattern for creating objects. It completely depends upon your implementation for creating objects.
- Abstract Factory can be used as an alternative to Facade to hide platform-specific classes.
- When Abstract Factory, Builder, and Prototype define a factory for creating the objects, we should consider the following points:
- Abstract Factory uses the factory for creating objects of several classes.
- Builder uses the factory for creating a complex object by using simple objects and a step-by-step approach.
- Prototype uses the factory for building an object by copying an existing object.
What is the difference between Factory Pattern & Abstract Factory Pattern?
The differences between Factory Pattern and Abstract Factory Pattern are given below:
Factory Pattern
Abstract Factory Pattern
Use inheritance and relies on a concrete class to create an object.
Use composition to delegate the responsibility of object creation to another class
Produce only one product
Produce a family of related products
Abstract Factory may use the Singleton design pattern for creating objects.
Abstract Factory may use Factory design pattern for creating objects.
It can also use a Builder design pattern and prototype design pattern for creating a product. It completely depends upon your implementation for creating products.
Hides the creation process of a single object
Hides the creation process of a family of related objects
What is the Singleton pattern?
The Singleton pattern is one of the simplest design patterns. This pattern ensures that a class has only one instance and provides a global point of access to it.
A singleton class includes:
- Static private Instance
- Private Constructor
- Public Instance Property or GetInstance() method
Explain the Singleton pattern with a real-life example?
Let’s take the following example where we need to create a singleton object of ExceptionHandler class to handle exceptions throughout the application.
public class ExceptionHandler { // .NET guarantees thread safety for static initialization private static ExceptionHandler _instance = null; // Lock synchronization object private static object _syncLock = new object(); private static StreamWriter _streamWriter; private ExceptionHandler() { _streamWriter = new StreamWriter(@"c:\Error.txt"); } public static ExceptionHandler Instance { get { // Support multi threaded applications through // 'Double checked locking' pattern which (once // the instance exists) avoids locking each // time the method is invoked lock (_syncLock) { if (_instance == null) _instance = new ExceptionHandler(); return _instance; } } } public void WriteLog(string message) { _streamWriter.WriteLine(message); _streamWriter.Flush(); } } public class Client { public void Add() { try { // TO DO: } } }
When to use the Singleton pattern?
Need a single instance of an object throughout the application. For example:
- Exception Logging
- Database Manager
What is the Builder pattern?
Builder pattern builds a complex object by using a step-by-step approach. The builder interface defines the steps to build the final object. This builder is independent from the object’s creation process. A class that is known as Director, controls the object creation process.
Moreover, the builder pattern describes a way to separate an object from its construction. The same construction method can create a different representation of the object.
For example, Construct Report/Email Builder Class with:
- Set Header
- Set Body
- Set Footer
Explain the Builder pattern with a real-life example?
Let’s take the following example where we need a report builder to generate reports.
C# Implementation Code:
public class ReportDirector { private readonly IReportBuilder _reportBuilder; public ReportDirector(IReportBuilder reportBuilder) { _reportBuilder = reportBuilder; } public void BuildStockReport() { _reportBuilder.BuildHeader(); _reportBuilder.BuildBody(); _reportBuilder.BuildFooter(); } } public interface IReportBuilder { void BuildHeader(); void BuildBody(); void BuildFooter(); public Report GetReport(); } public class ReportBuilder : IReportBuilder {public void BuildBody() { _report.Body = string.Join(Environment.NewLine, _products.Select(p => $"Product name: {p.Name}, product price: {p.Price}")); } public void BuildFooter() { _report.Footer = "\nReport provided by the IT_PRODUCTS company."; } public Report GetReport() { var report = _report; Clear(); return report; } private void Clear() => _report = new Report(); } public class Product { public string Name { get; set; } public double Price { get; set; } } public class Report { public string Header { get; set; } public string Body { get; set; } public string Footer { get; set; } public override string ToString() => new StringBuilder() .AppendLine(Header) .AppendLine(Body) .AppendLine(Footer) .ToString(); }
When to use the Builder pattern?
The abstract factory pattern is helpful in the following cases:
- Need to create an object in several steps (a step-by-step approach).
- The creation of objects should be independent of the way the object's parts are assembled.
- Runtime control over the creation process is required.
Summary
NET Design Patterns interview Book is the perfect resource for anyone who wants to ace their next programming interview. The book starts with Design Patterns Principles, including Gang of Four patterns. It then provides a comprehensive set of interview questions for each pattern, ranging from easy to hard. The questions are accompanied by clear explanations and sample code, making it easy to understand both the concept and the implementation.
The book contains more than 140+ different questions regarding the use cases and real-world code implementation of various design patterns that we have come across while working with .NET technologies.