The principle; is a high level idea that born out of various minds and brains, it is recognized based on human experiences and experiments, it inspires other folks when they try to take similar actions or decisions. The critical thinking; is the curiosity and carefulness of providing full fledged set of solutions free of mistakes, errors, accidents and problems.
SOLID are a group of principles that guide critical thinking of software developers and programmers while they are developing a software code. Nevertheless, being aware of these principles guarantees a common understanding between developers about the decisions they usually made in the pattern body of their code. As result, a group of design patterns have come out of those principles, which progressively became popular and known to be best practices, currently there exist a set of known design patterns based on SOLID principles, they are out of the scope of this article, and I'll write about them latter on.
In this article I will try to talk a while about each principle and spot light on some practical examples that are already existed in .NET framework.
S: SRP Priniciple <<a class should only take care of only one responsibility>>
SRP is an abbreviation of "Single Responsibility" principle, this principle tells the developer to write his class for only one and single target, his class should take care of only one responsibility, it should has only one reason to change, it should does one thing and does it very well. So if it is an entity class, then it should only contains properties to hold values, without performing any business functionalities, data persisting functionalities or even UI functionalities. In case it is a provider class, it should only contains the persisting functionality of the data, like opening a connection with the DB server, executing SQL-commands or preventing security threatens on data. This principle is essentially required so that you have a clean code that is easy to be maintained by other developers. They will find it readable and easily approachable, they can move from generic-function to another without losing concentration of their purpose. They will find it also reusable, so your class will help others to reduce effort and consequently reduce the overall cost. One example of S-Principle in .NET framework is the "Math" class, which is a static class that can't be instantiated, its only purpose is to provide a collection of mathematical functions, these functions are available to be used in any context. Actually, any class in .NET framework is eligible to satisfy the "Single Responsibility" principle.
O: OCP Principle <<a class is opened for extension but closed for modification>>
OCP is an abbreviation of "Open Close" principle, this principle tells the developer to design his class so another developer "may be himself" is not allowed to modify its functionality but he is allowed to extend it. The class should be opened for extension but closed for modification. The creator-developer can use many techniques to provide such facility, like virtual methods, callback functions, events, delegates, actions or lambda. The user-developer in the other hand-side can use the corresponding techniques to extend the class functionality, like higher order functions technique, which is a functional style programming that allows him to pass his extension-functions as a parameter to the class's methods, so the class's methods will call his extension-functions to modify , format or evaluate the values of its properties. The .Net framework has provided the concept of the Extension-Methods to support "Open Close" principle, which allows us to extend any class using simple coding-style with static class, public static functon and "this" keyword. The most populare example in .Net is LINQ that extends IEnumerable with large number methodes like Where(), Join() and OrderBy(). IEnumerable is "closed" to be modified but is "opened" for extending by LINQ or any custom extensions.
L: LSP Principle <<child class object should be able to replace parent class object in any context>>
LSP is an abbreviation of "Liskov Substitution" principle, this principle is introduced by Barbara Liskov "American scientist" in 1987, the principle emphasizes on the deep relationship between the suppertype and its subtype, so that each of them should respect the state of the other, the subtype "derived class or child class" should respect the state of the suppertype "supper class or parent class". If a developer do respect this principle then he can safely use the subtype's object in any context where suppertype's object is expected. So, this principle is all about a parent class object that can be able to refer his child object during run time without any problem, and the child class object can replace the parent class object during run time with any problem. The most popular example in .NET is the "Object" class that is the parent of all classes in .NET, the Object's methods are available in every class such as "GetHashCode(), GetType(), Equals(), ToString() and Finalize()", and any class object can change the behavior of any of these functions using "override" keyword, but they can't change any certain state of the type "Object", so that the child object safely replaces "Object" type in any context.
I: ISP Principle <<clients should not be forced to depend on methods that they do not use>>
ISP is abbreviation of "Interface Segregation" principle, this principle tells the developer to simplify the role of each interface, in order to keep the interface untouchable in front of any change requests in the future, and clients remain stable once they build their systems depending on this interface. This way, we will have different interfaces for different roles, the developer implementation may be accumulated in one class that inherits from multiple interfaces, hence, the client will not be forced to use an interface if he does not need it. It is all about identify small roles instead of one generic role, and assign every little role to an individual interface so we don't define too much methods for any individual interface. In .NET for example, we can find interfaces like IEquitable<T>, IComparable<T> and IEnumerable<T> that has only one method each, which are Equals(T), CompareTo(T) and GetEnumerator(). It was possible for the three functions to be accumulated into one interface, but that will enforce clients to depend on methods that they will not use. Being segregated is more easy to extend any one of them and left the others, it is also possible to implement or inherit one of them or more.
D: DIP Principle <<high level modules should not depend on low level modules, instead both should depend upon abstractions>>
DIP is abbreviation of "Dependency Inversion" principle, this principle tells the developer to give himself a freedom to switch or swap dependencies during run-time or compile-time instead of tightly coupling between modules or classes at design-time, that will only be achieved by abstracting the dependency between modules, so if we have the screen and the printer as output modules then we can make our copier module depends on abstracted output module at run time that will be assigned one of them to copy on. This is sometimes named dependency injection and is important to develop loosely coupled software systems. The .NET has introduced "Generic & Interface" as a very efficient techniques for dynamic dependencies whether at compiling time or at run time. That way we can have a reusable and extendable modules that are convenient with testing as well.