SOLID Principles
Tags: csharp, dotnet, programming, beginners
Published: 2023-03-30 06:57:22

Solid principles provide a foundation for writing high-quality code. By adhering to these principles, developers can create code that is efficient, maintainable, clean and scalable. Rather than delving into theory, let's examine how these principles can be applied in practice.

### **S - Single Responsibility Principle (SRP)**

The Single Responsibility Principle states that a class or module should have only one reason to change. In simple words, a class or library should be responsible for only one functionality or task.

Let's understand it with code, here we have a class it has implementation as shown in code.

```csharp
public class Users
{
    public string UserId { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public string ProductDescription { get; set; }

    public void AuthenticateUser(string _username, string _password)
    {
        //Code implementation to authenticate user
    }

    public void AddProduct(int _productId, string _productName, string         _productDescription)
    {
        //Code implementation to add product
    }
}
```

Here the class **User** contains functionality as well as properties of **User** as well as of **Product**, which is not a scalable and maintainable approach.

As per SRP, we divide the functionality of **User** and **Product** so they do not interfere with each other's functionality, and also the class with have only reason to change when there is an addition or any modification related to it.

```csharp
public class Users
{
    public string UserId { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }

    public void AuthenticateUser(string _username, string _password)
    {
        //Code implementation to authenticate user
    }
}
```

```csharp
public class Products
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public string ProductDescription { get; set; }

    public void AddProduct(int _productId, string _productName, string _productDescription)
    {
        //Code implementation to add product
    }
}
```

### **O - Open/Closed Principle (OCP)**

The Open/Closed Principle states that classes or modules should be open for extension but closed for modification. In other words, once a class or module is created, it should not be modified directly. Instead, new functionality should be added through new code that extends the existing code.

Let us check an example of how OCP works.

```csharp
public static class ExtensionClass
{
	public static bool Rename(this FileInfo fileInfo, string newName)
	{
		try
		{
			fileInfo.MoveTo(newName);
		}
		catch (Exception)
		{
			return false;
		}
		
		return true;
	}
}
```

We have added an extension method to **FileInfo** class which is available in the **System.IO** namespace, and it does not contain any method named **Rename**, so we have extended the **FileInfo** class.

```csharp
FileInfo file = new FileInfo("test.txt");
file.Rename("newname.txt");
```

Now, **Rename** method can be accessed by creating an object of **FileInfo.**

### **L - Liskov Substitution Principle (LSP)**

Liskov's Substitution Principle states that objects of a superclass should be able to be replaced with objects of a subclass without any problem.

The idea behind the LSP is to make code more flexible and easier to maintain. By ensuring that subclasses can be used interchangeably with their parent classes, we can write code that is more modular and easier to extend.

Let us understand it with a simple example

```csharp
public class SuperHero
{
	//Implementation of SuperHero
}

public class Batman : SuperHero
{
	//Implementation of Batman
}
```

Here we have a **SuperHero** and **Batman,** where **SuperHero** is the base class and **Batman** is inheriting properties of **SuperHero**. As per Liskov Substitution Principle, we can initialize **SuperHero** with an instance of **Batman**.

```csharp
SuperHero super = new Batman();
```

### **I - Interface Segregation Principle (ISP)**

The Interface Segregation Principle states that no client should be forced to depend on methods it does not use. Segregation in simple words means separation.

In simpler words, this means that when creating **interfaces**, we should make sure that they are focused on specific functionality and only include methods that are relevant to that functionality. **Classes** that implement the **interface** should not be required to implement methods that they don't need or use.

```csharp
public interface IDemoInterface
{
	void AddUser();
	void DeleteUser();
	string GetUser(int UserId);
	void AddProduct();
	void DeleteProduct();
	string GetProduct(int ProductId);
	double GetPrice(int ProductId);
}
```

Here in the above code if we want to implement **IDemoInterface** into our class that has user management functionality, we only require some implementation, rather than other unnecessary methods related to product management. So we can separate/segregate the interface, so we implement relevant functionality.

```csharp
public interface IUser
{
	void AddUser();
	void DeleteUser();
	string GetUser(int UserId);
}
```

```csharp
public interface IProduct
{
	void AddProduct();
	void DeleteProduct();
	string GetProduct(int ProductId);
	double GetPrice(int ProductId);
}
```

### **D - Dependency Inversion Principle (DIP)**

The Dependency Inversion Principle suggests that high-level modules should not depend on low-level modules directly, but instead, both should depend on abstractions.

In simpler terms, DIP is about designing software in a way that allows for easy changes in the future. By abstracting away the implementation details of low-level components, changes to those components will not affect the high-level components that depend on them. This makes it easier to make changes to the system without causing unintended consequences.

Let's understand it using a simple example.

```csharp
interface IDirectory
{
	List<string> GetDirectories();
}

public class FTPDirectory : IDirectory
{
	public List<string> GetDirectories()
	{
		//Implementation to get FTP Directories
	}
}

public class LocalDriveDirectory : IDirectory
{
	public List<string> GetDirectories()
	{
		//Implementation to get Local drive Directories
	}
}
```

Here we have an interface, which is being implemented by two classes **FTPDirectory** and **LocalDriveDirectory**. In the below code, we are injecting dependency using a constructor and accessing methods of Interface **IDirectory,** which is an abstraction for low-level classes **FTPDirectory** and **LocalDriveDirectory**.

```csharp
public class DirectoryExplorer
{
	private readonly idirectory _directory;

	public directoryexplorer(idirectory directory)
	{
		_directory = directory;
	}

	public list<string> listdirectories()
	{
		return _directory.getdirectories();
	}
}
```

By using Dependency Inversion Principle, our high-level class is not directly dependent on low-level classes but depends on abstraction. This ensures decoupled, flexible and modular code. We can create instances of Directory Explorer in the following ways, based on our requirements.

```csharp
DirectoryExplorer directoryExplorer = new DirectoryExplorer(new FTPDirectory());
```

```csharp
DirectoryExplorer directoryExplorer = new DirectoryExplorer(new LocalDriveDirectory());
```

Thank you for taking the time to read our first blog post on writing clean code.

If you have any thoughts or suggestions on how we can improve our blog posts, please don't hesitate to let us know. We're always looking for ways to enhance our content and make it more engaging for our readers. If there's a topic you'd like us to cover in a future blog post, we'd love to hear your ideas.

You can leave a comment below or contact us directly through our website to share your thoughts. We look forward to hearing from you and continuing to provide valuable content for our readers.