SOLID Principles in C#

Bhone Myint Kyaw
7 min readJul 10, 2022

--

Solid Principle

SOLID design principles in C# are basic design principles. SOLID is a series of design recommendations for crafting reliable, understandable, flexible, and maintainable software. SOLID is an acronym for the following five principles:

  • Single Responsibility Principle
  • Open closed Principle
  • Liskov substitution principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Software developers leverage SOLID principles to write clean code.

Common Software Issues

Classes with too many responsibilities assigned to them are hard to debug. Tight coupling between classes makes adding new features a challenge. And code duplication

Common Solutions

Choosing the appropriate clean architecture for the application, for example MVC, 3-tier, Onion, MVP, and so on. Following the SOLID Design Principles. And applying the proper Design Patterns.

Advantages of SOLID Principles

  • Maintainability
  • Testability
  • Flexibility
  • Agile development
  1. Single Responsibility Principle (SRP)

This principle recommends “Every software module should have only one reason to change”. This means that every class or similar structure in your code has only one job to do and should not multitask like a swiss knife. When classes are smaller and contain cleaner code it is easier to maintain and if we change one part of our system, we wouldn’t need to make several other changes because of the fact that the part we’ve changed is only responsible for a single thing.

Note

  • A responsibility = A reason to change the code
  • Multiple responsibilities = multiple reasons to change code
  • Cohesion: How strongly related the various responsibilities of a module
  • Coupling: The degree to which each module relies on other modules
  • We should strive for low coupling and high cohesion.
Violating SRP

This piece of code is valid as it will

  1. Add Attendance
  2. Delete Attendance
  3. Send Email
  4. Log Information

However, it does not follow the SRP principles as our class has logic for upsert Attendance, sending mail, and logging information and has too many responsibilities.

Common issues

  • Difficult to understand and maintain
  • Difficult to test functionalities and does not facilitate test-driven development
  • High probability of code duplication
Achieved SRP

However, we can refactor like in the above pic so that there is a class that is solely responsible for Attendance insert and update and other classes that are responsible for Sending email and logging email.

Advantages of SRP

  • If a single functionality breaks, we know easily which class has a bug
  • Classes will be much easier to understand and implement than ones with multi functions
  • We can easily change the behavior of a function by editing the single class responsible for it.

2. Open/Closed Principle

This principle recommends “A software module/class is open for extension and closed for modification”. This means that for new requirements, we extend the software modules with new behaviors and we should not modify the existing code except for bug fixing.

Violating OCP

This piece of code is valid but violates the OCP principle as we need to modify the existing code when a new payment type is required to add.

Common Issues

  • Adding new features can introduce bugs and requires re-testing the existing application as well as the new features.
  • Implementing all the features in one class make maintenance of the class very laborious
  • Changes that cascade through many modules in the application

How to implement OCP

  • Parameters- OCP can be achieved via parameters
  • Inheritance
  • Abstraction
  • Composition/Injection
Achieved OCP

Advantages of OPEN/CLOSE

  • Code maintenance is easier as it reduces the risk of breaking the existing implementation.
  • Less likely to introduce bugs in the codebase
  • Confidence that new changes will not affect the parent or any other derived class

Implementation Guidelines

  • At first, implement the solution using concrete code
  • Secondly, try to brainstorm all the future requirements/features
  • Modify code to be extensible without the need to modify its source each time
  • Abstraction adds complexity to the code. Balance abstraction and concreteness

3. Liskov substitution principle

The Liskov Substitution Principle (LSP) states that “you should be able to use any derived class instead of a parent class and have it behave in the same manner without modification”. It recommends that any derived class should be perfectly interchangeable with the parent class. “Ability to replace any instance of a parent class with an instance of one of its child classes without negative side effects”

LSP advocates that the IS-A relationship is insufficient and should be replaced with IS-SUBSTITUTABLE. Ensure substitutability is retained between each derived class and base.

Violating LSP

If we use the subclass Orange to create an apple instance, we will get the ‘Orange’ output. This is totally incorrect as apples aren’t supposed to be orange colored.

Common Issues

  • Non-substitutable derived code break polymorphism
  • Usually, client code expects child classes to be substitutable of their base classes
  • Using if-then or switch statements to resolve substitutability problems make maintenance harder
Achieved LSP

We can achieve the LSP by refactoring the code like in the above, we could create a Fruit Parent class, and both apples and oranges will inherit from it. Apple will inherit directly from Fruit and Orange will also inherit from Fruit.

This way both subclasses will have their own methods if needed and can replace the parent class without any changes in the result

Advantages of LISKOV

  • Non-substitutable code violates polymorphism
  • Conformance to LSP allows for proper use of polymorphism

4. Interface Segregation Principle

The Interface Segregation Principle states that clients should not be forced to implement interfaces they don’t use. So instead of huge interfaces, it is better to create multiple small ones so that they are utilized in the best possible way.

Violating ISP

We are violating ISP as we are focing to implement StartAIAssistant to DangonJeep car and only a few cars like Tesla can support that.

Common Issues

  • More dependencies means more coupling, more brittle code and more testing
  • Interface Segregation violations result in classes that depend on things they do not need.
  • This reduces flexibility when adding new features and maintainability

How to Apply ISP

  • Break up large interfaces into smaller ones
  • Don’t control large interface with exceptions
  • Apply Adapter design pattern
Achieved ISP

We can achieved the ISP by refactoring the code by spilitting the interface. Now tesla car can implement IAICar to obtain the AIAssistant and DagonJeep which cannot provide AiAssistant can implement ICar.

Advantages of ISP

  • Abstraction increases the application security by showing the most essential details of an object through an interface
  • A class can implement multiple interfaces with each interface separated with a comma
  • An interface acts as a contract which forces the classes to implement all the methods.

5. Dependency Inversion Principle

The Dependency Inversion Principle (DIP) states that high-level modules/classes should not depend on low-level modules/classes. Both should depend upon abstractions. Secondly, abstractions should not depend upon details. Details should depend upon abstractions. Abstractions should not depend on concrete implementations, instead concrete implementations should depend on abstractions.

What are interfaces?

In C# an interface help to achieve data abstraction. By default interface members are abstract and public and interface is just a contract for classes to implement.

Violating DIP

We are violating DIP principle as the Employee class is directly dependended on SalaryCalculator. When the new salarycalculator is introduced, we need to modify the employee class.

Achieved DIP

We can achieve the DIP by refactoring the code by implementing the ISalaryCalculator in the Employee class instead of concrete class. And we inject the interface reference in the Employee class. Now the higher level class employee has no reference on the lower level and depended only on interface. We need to inject the concrete implementation when we instantiate the Employee.

Advantages

  • Abstraction increase the application security by showing the most essential details of an object through an interface
  • A class can implement multiple interfaces with each interface separated with a comma

--

--