Skip to content

Instantly share code, notes, and snippets.

@jv-amorim
Last active September 14, 2022 11:26
Show Gist options
  • Save jv-amorim/860ef67c250620f3f123aa7a09adf6c1 to your computer and use it in GitHub Desktop.
Save jv-amorim/860ef67c250620f3f123aa7a09adf6c1 to your computer and use it in GitHub Desktop.
Notes about SOLID principles in C#.

📌️ C# SOLID Notes

Content:

  • SOLID;
  • Single Responsibility Principle (SRP);
  • Open/Closed Principle (OCP);
  • Liskov Substitution Principle (LSP);
  • Interface Segregation Principle (ISP);
  • Dependency Inversion Principle (DIP).

💎️ SOLID:

SOLID Principles are five fundamental principles of object-oriented design that keeps the software loosely coupled, testable, and maintainable.

We don't must try to apply every SOLID principle upfront. It's better use them to eliminate pain by improving the design after we have written some working software.


🔹️ Single Responsibility Principle (SRP):

SRP states that each class should have one responsibility, which means it should have one reason to change. Each software module (classes, methods, etc.) should have one and only one reason to change.

Classes should encapsulate doing a particular task in a particular way. When they're single-purpose, they're usually perfectly suited to this purpose and easy to use. Other classes can delegate the specific task to an instance of a class that encapsulates performing that task. When classes do too many things, they often end up coupling together things that shouldn't be related and make the overall class much harder to use. Trying to provide a general purpose tool results in additional complexity that a single-purpose tool wouldn't require.

What is a responsibility? It's a decision our code is making about the specific implementation details of some part of what the application does. It's the answer to the question of how something is done. Some examples of responsibilities include persistence, logging, validation, and, of course, business logic.

The single responsibility principle is closely related to the concept of coupling. When two or more details are intermixed in the same class, it introduces tight coupling between these details. If the details change at different times for different reasons, it's likely to cause problems in the future with code churn in the class in question. Loose coupling refers to approaches that can be used to support having different details of the application interact with one another in a modular fashion. Typically, one class will be responsible for some higher-level concern and will delegate to other classes who are responsible for the details of how to perform lower-level operations. This follows another principle, separation of concerns.

Separation of concerns suggests that programs should be separated into distinct sections that each address a separate concern or set of information that affects the program. A key benefit of following separation of concerns is that high-level business logic avoids having to deal with such low-level code. Ideally, high-level code should not know about how low-level implementation details are implemented, nor should it be tightly coupled to the specific details.

Another concept that is closely related to coupling is cohesion. Cohesion describes how closely related elements of a class or module are to one another. Classes that have many responsibilities will tend to have less cohesion than classes that have a single responsibility. Relationships between classes represent coupling. This coupling might be tight or loose depending on how it is implemented. In most cases, loose coupling is preferred because it results in code that is easier to change and test.


🔹️ Open/Closed Principle (OCP):

"You should be able to extend the behavior of a system without having to modify that system." - Bertrand Meyer.

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification. It should be possible to change the behavior of a method without editing its source code. Open to extension in this case means that it's easy to add new behavior. If a module in our application is closed to extension, it simply means that it has fixed behavior. We can't change it. On the other side, closed to modification means that it's unnecessary to change the source or binary code of the system. Code that is closed to extension can't be changed without changing the code itself. So by definition, it is not closed to modification.

Packages and plugins are a good example of OCP (closed for modification, open to extension), since the original source code is not modified or recompiled, but extended.

In the SRP you make a judgement about decomposition and where to draw encapsulation boundaries in your code. In the OCP, you make a judgement about what in your module you will make abstract and leave to your module’s consumers to make concrete, and what concrete functionality to provide yourself.

Typical approaches to OCP: parameters; inheritance; composition/injection; extension methods (in C#). Examples:

(Extremely Concrete:)

public class DoOneThing
{
	public void Execute()
	{
		Console.WriteLine("Hello World.");
	}
}

✅️ Parameter-Based Extension:

public class DoOneThing
{
	public void Execute(string message)
	{
		Console.WriteLine("Hello World.");
	}
}

✅️ Inheritance-based Extension:

public class DoOneThing
{
	public virtual void Execute()
	{
		Console.WriteLine("Hello World.");
	}
}

public class DoAnotherThing : DoOneThing
{
	public override void Execute()
	{
		Console.WriteLine("Goodbye World.");
	}
}

✅️ Composition/Injection Extension:

public class DoOneThing
{
	private readonly MessageService _messageService;

	public DoOneThing(MessageService messageService)
		=> _messageService = messageService;

	public void Execute()
	{
		Console.WriteLine(_messageService.GetMessage());
	}
}

✅️ It's preferable to implement new features in new classes, the reasons are very clear:

  1. With a new class, it's possible design this class to suit a problem in hand;

  2. Nothing in current system depends on it;

  3. Can add behavior without touching existing code (very useful in legacy systems);

  4. Can follow the Single Responsability Principle;

  5. Can be unit-tested.

✅️ Some tips when using OCP:

  1. Solve the problem first using simple, concrete code;

  2. Identify the kinds of changes the application is likely to continue needing;

  3. Modify code to be extensible along the axis of change you have identified.

✅️ More OCP content:


🔹️ Liskov Substitution Principle (LSP):

Subtypes must be substitutable for their base types. LSP states that the "is a" relationship (inheritance) is insufficient and should be replaced with "is substitutable for".

(Not finished...)


🔹️ Interface Segregation Principle (ISP):

The I in SOLID is the interface-segregation principle. This says that clients should not be forced to depend upon methods that they do not use. Interfaces belong to clients, not to hierarchies. This translates into interfaces should only include what the calling code needs. If the code calling the interface is only using a small fraction of the methods, then that indicates that the interface may need to be broken down into smaller pieces.

(Not finished...)


🔹️ Dependency Inversion Principle (DIP):

(Not finished...)

@demndevel
Copy link

when you will finish it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment