Object oriented programming (or OOP) is a style of programming that encapsulates data and behaviours into models known as objects. In this way, related code is grouped together and kept separate from other code, and provides reusable blocks that can be used to rationalise the problem at hand. OOP is probably one of the most common forms programming, and many popular programming languages, including C#, Java, and JavaScript, are built with this in mind. In a lot of languages, the blueprint for the object is known as a class. The class contains all the definitions for that object, including properties and functions (more commonly known as methods). To create an actual object, we say that we create an instance of the class, in which all of the property definitions are given actual values. Importantly, we can have multiple instances of the same class existing at the same time, each with different values. For example, we could create a simple Person class that has a name property and a speak method: { { Name = name; } Id { ; ; } Name { ; ; } { Console.WriteLine( ); } } public class Person ( ) public Person name string public int get set public string get set ( ) public void Speak $"My name is " {Name} This is the blueprint. Now we can create two separate instances of the class, and the speak methods will give different outputs because each actual object is instantiated with a different value for the name property. bernard = Person( ); sally = Person( ); bernard.Speak(); sally.Speak(); var new "Bernard" var new "Sally" // outputs "My name is Bernard" // outputs "My name is Sally" Although these are very simple examples, hopefully you can see the benefit of being able to create these object blueprints and create separate instances of actual objects. However, it can be easy to misuse objects and create difficult-to-read or unstable code, if you're not careful. For example, you could potentially end up with a "God" class that has lots of unrelated behaviour crammed into the same object. To help write good, maintainable, stable code, the SOLID principles have been described below. Single Responsibility Principle S - - Open/closed principle O - Liskov substitution principle L - Interface segregation principle I - Dependency inversion principle D S - Single Responsibility Principle The title here is pretty self-explanatory. A class should only have one responsibility. This is to avoid the "God" class scenario, where one class does everything, and helps split up your code into smaller, sensible chunks. Although the single responsibility principle, is quite easy to understand, in practice it is not always so simple to spot when something belongs in a class and when it should be moved to a different class. Making this judgement is mostly a matter of experience, and you will get better at it with time. If we go back to our Person class, we might decide that we want to save each person into a database. Therefore, it might seem sensible to create a method on the Person class: { { } } public class Person // ... other properties and methods ( ) public void SaveToDatabase // logic to save person However, the problem here is that the details of how to save a person to the database is an additional responsibility, and so that responsibility should be moved to another class. Therefore, we could create a database class, with a SavePerson method, and pass the instance of the Person into that class. That way the Person class only deals with the details of the person, and the database class deals with the details of saving the person. { SavePerson(Person person) { } } public class Database public void // logic to save person O - Open/closed principle The open/closed principle states that an object should be open for extension but closed for modification. This means that you should design you objects in such a way that they can be easily extended, without having to directly modify them. For example, we could define two new classes, Employee and Manager, which are derived from the Person class, and a SalaryCalculator class. : { } : { } { { (person Employee) { * ; } (person Manager) { * ; } } } public class Employee Person // employee methods and properties public class Manager Person // manager methods and properties public class SalaryCalculator ( ) public decimal CalculateSalary Person person if is return 100 365 else if is return 200 365 In this example, the SalaryCalculator class violates the open/closed principle because, if we extend the program by adding a Director class, we would have to modify the CalculateSalary method to account for this. To fix this, we could add a DailyRate property to each of the Person types. That way, we can add as many Person types as we want, and never have to modify SalaryCalculator. { DailyRate => ; } : { DailyRate => ; } : { DailyRate => ; } : { DailyRate => ; } { { person.DailyRate * ; } } public class Person public virtual decimal 0 public class Employee Person public override decimal 100 public class Manager Person public override decimal 200 public class Director Person public override decimal 300 public class SalaryCalculator ( ) public decimal CalculateSalary Person person return 365 L - Liskov substitution principle The Liskov substitution principle states that every sub-class should be substitutable for it's base class and the program will still behave as expected. The example for the open/closed principle is also a good example for the Liskov substitution principle. In the SalaryCalculator class, the method takes the base class Person but at runtime we can pass any of its sub-classes too. Because we have used the virtual and override keywords in the Person and sub-classes respectively, the value of the DailyRate inside the CalculateSalary method will be that of the sub-class i.e even though we have substituted the sub-classes for the base class in the CalculateSalary method, the method still behaves correctly. As an example of violating the Liskov substitution principle, we could redefine our classes as: { DailyRate => ; } { => ; } public class Person public decimal 0 public class Employee ( ) DailyRate public new decimal 100 With this definition we violate the Liskov substitution principle because the DailyRate will always evaluate to 0 in the SalaryCalculator and not the value of the sub-class. I - Interface segregation principle The interface segregation principle states that a client should not be forced to implement properties and methods of an interface that it will not use. Therefore, it is better to define lots of small, specific interfaces, rather than few large, general interfaces. As an example, we could define an IRepository interface for performing CRUD operations for our Person class on a database and implement it: { ; ; ; ; ; } : { { } { } } public interface IRepository ( ) bool Create Person person Person ( ) Get id int IEnumerable<Person> ( ) GetAll ( ) bool Update Person person ( ) bool Delete id int public class Repository IRepository ( ) public bool Create Person person // create logic Person ( ) public Get id int // get logic // etc This is fine if we know that we will always need full CRUD functionality. But what if actually, we only require the repository to be readonly? At the minute, we're forced to implement the full CRUD operations, even if we don't need them. Instead we can define multiple interfaces, and only implement the ones that we need. { ; } { ; ; } { ; } { ; } public interface ICreatableRepository ( ) bool Create Person person public interface IGettableRepository Person ( ) Get id int Person ( ) GetAll public interface IUpdateableRepository ( ) bool Update Person person public interface IDeletableRepository ( ) bool Delete id int Then we can optionally choose to make a implement readonly repository or a full CRUD repository. : { { } { } } : , , , { { } { } } public class ReadonlyRepository IGettableRepository Person ( ) public Get id int // get logic IEnumerable<Person> ( ) public GetAll // get all logic public class CrudRepository ICreateableRepository IGettableRepository IUpdateableRepository IDeleteableRepository ( ) public bool Create Person person // create logic Person ( ) public Get id int // get logic // etc D - Dependency Inversion principle The dependency inversion principle states that classes shouldn't depend on other classes, but instead should depend on the interfaces that those classes implement. This has the effect of inverting the direction of dependencies. For example, a traditional 3-tier app consisting of only classes might have a PersonPresenter class, which depends on a PersonLogic class, which depends on a PersonRepository class. E.g. PersonPresenter --> PersonLogic --> PersonRepository The problem with this is that it tightly couples the high level presentation to the low level implementation details. It's much better to have loosely coupled code, which can be achieved using interfaces. To invert the dependencies, we can create interfaces of each of the low level classes, and have those classes implement them: PersonPresenter --> IPersonLogic <-- PersonLogic --> IPersonRepository <-- PersonRepository Conclusion So there are the 5 SOLID principles of Object Oriented Programming. If you've found it useful, please like and share this post. And feel free to follow me on . Twitter Previously published at https://samwalpole.com/learn-the-solid-principles-for-object-oriented-programming