What is SOLID principles?
SOLID principles are the design principles that enable us to manage most of the software design problems. Robert C. Martin compiled these principles in the 1990s. These principles provide us with ways to move from tightly coupled code and little encapsulation to the desired results of loosely coupled and encapsulated real needs of a business properly. SOLID is an acronym of the following.
S: Single Responsibility Principle (SRP)
O: Open closed Principle (OCP)
L: Liskov substitution Principle (LSP)
I: Interface Segregation Principle (ISP)
D: Dependency Inversion Principle (DIP)
- public class UserService
- {
- public void Register(string email, string password)
- {
- if (!ValidateEmail(email))
- throw new ValidationException("Email is not an email");
- var user = new User(email, password);
- SendEmail(new MailMessage("mysite@nowhere.com", email) { Subject="HEllo foo" });
- }
- public virtual bool ValidateEmail(string email)
- {
- return email.Contains("@");
- }
- public bool SendEmail(MailMessage message)
- {
- _smtpClient.Send(message);
- }
- }
- public class UserService
- {
- EmailService _emailService;
- DbContext _dbContext;
- public UserService(EmailService aEmailService, DbContext aDbContext)
- {
- _emailService = aEmailService;
- _dbContext = aDbContext;
- }
- public void Register(string email, string password)
- {
- if (!_emailService.ValidateEmail(email))
- throw new ValidationException("Email is not an email");
- var user = new User(email, password);
- _dbContext.Save(user);
- emailService.SendEmail(new MailMessage("myname@mydomain.com", email) {Subject="Hi. How are you!"});
- }
- }
- public class EmailService
- {
- SmtpClient _smtpClient;
- public EmailService(SmtpClient aSmtpClient)
- {
- _smtpClient = aSmtpClient;
- }
- public bool virtual ValidateEmail(string email)
- {
- return email.Contains("@");
- }
- public bool SendEmail(MailMessage message)
- {
- _smtpClient.Send(message);
- }
- public class Rectangle{
- public double Height {get;set;}
- public double Wight {get;set; }
- }
- public double TotalArea(Rectangle[] arrRectangles)
- {
- double area;
- foreach(var objRectangle in arrRectangles)
- {
- area += objRectangle.Height * objRectangle.Width;
- }
- return area;
- }
- }
Hey, we did it. We made our app without violating SRP. No issues for now. But can we extend our app so that it could calculate the area of not only Rectangles but also the area of Circles as well? Now we have an issue with the area calculation issue because the way to do circle area calculation is different. Hmm. Not a big deal. We can change the TotalArea method a bit so that it can accept an array of objects as an argument. We check the object type in the loop and do area calculation based on the object type.
- public class Rectangle{
- public double Height {get;set;}
- public double Wight {get;set; }
- }
- public class Circle{
- public double Radius {get;set;}
- }
- public class AreaCalculator
- {
- public double TotalArea(object[] arrObjects)
- {
- double area = 0;
- Rectangle objRectangle;
- Circle objCircle;
- foreach(var obj in arrObjects)
- {
- if(obj is Rectangle)
- {
- area += obj.Height * obj.Width;
- }
- else
- {
- objCircle = (Circle)obj;
- area += objCircle.Radius * objCircle.Radius * Math.PI;
- }
- }
- return area;
- }
- }
Wow. We are done with the change. Here we successfully introduced Circle into our app. We can add a Triangle and calculate it's the area by adding one more "if" block in the TotalArea method of AreaCalculator. But every time we introduce a new shape we need to alter the TotalArea method. So the AreaCalculator class is not closed for modification. How can we make our design to avoid this situation? Generally, we can do this by referring to abstractions for dependencies, such as interfaces or abstract classes, rather than using concrete classes. Such interfaces can be fixed once developed so the classes that depend upon them can rely upon unchanging abstractions. Functionality can be added by creating new classes that implement the interfaces. So let's refract our code using an interface.
- public abstract class Shape
- {
- public abstract double Area();
- }
- public class Rectangle: Shape
- {
- public double Height {get;set;}
- public double Width {get;set;}
- public override double Area()
- {
- return Height * Width;
- }
- }
- public class Circle: Shape
- {
- public double Radius {get;set;}
- public override double Area()
- {
- return Radius * Radus * Math.PI;
- }
- }
- public class AreaCalculator
- {
- public double TotalArea(Shape[] arrShapes)
- {
- double area=0;
- foreach(var objShape in arrShapes)
- {
- area += objShape.Area();
- }
- return area;
- }
- }
Now our code is following SRP and OCP both. Whenever you introduce a new shape by deriving from the "Shape" abstract class, you need not change the "AreaCalculator" class. Awesome. Isn't it?
L: 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 ensures that a derived class does not affect the behavior of the parent class, in other words,, that a derived class must be substitutable for its base class.
This principle is just an extension of the Open Closed Principle and it means that we must ensure that new derived classes extend the base classes without changing their behavior. I will explain this with a real-world example that violates LSP.
A father is a doctor whereas his son wants to become a cricketer. So here the son can't replace his father even though they both belong to the same family hierarchy.
Example: Without using the Liskov Substitution Principle in C#:
Let us first understand one example without using the Liskov Substitution Principle in C#. In the following example, first, we create the Apple class with the method GetColor. Then we create the Orange class which inherits the Apple class as well as overrides the GetColor method of the Apple class. The point is that an Orange cannot be replaced by an Apple, which results in printing the color of the apple as Orange as shown in the below example.
As you can see in the above example, Apple is the base class and Orange is the child class i.e. there is a Parent-Child relationship. So, we can store the child class object in the Parent Reference variable i.e. Apple apple = new Orange(); and when we call the GetColor i.e. apple.GetColor(), then we are getting the color of the Orange not the color of Apple. That means once the child object is replaced i.e. Apple storing the Orange object, the behavior is also changed. This is against the LSP Principle. The Liskov Substitution Principle in C# states that even the child object is replaced with the parent, the behavior should not be changed. So, in this case, if we are getting the color of Apple instead of Orange, then it follows the Liskov Substitution Principle. That means there is some issue with our software design. Let us see how to overcome the design issue and makes the application follow Liskov Substitution Principle.
Example Using the Liskov Substitution Principle in C#
Let’s modify the previous example to follow the Liskov Substitution Principle. Here, first, we need a generic base class such as Fruit which is going to be the base class for both Apple and Orange. Now you can replace the Fruit class object with its subtypes either Apple and Orage and it will behave correctly. Now, you can see in the below code, we created the super Fruit class as an abstract class with the GetColor abstract method and then the Apple and Orange class inherited from the Fruit class and implement the GetColor method.
Now, run the application and it should give the output as expected. Here we are following the LSP as we are now able to change the object with its subtype.
Interface Segregation Principle (ISP) :
1. First, no class should be forced to implement any method(s) of an interface they don’t use.
2. Secondly, instead of creating large or you can say fat interfaces, create multiple smaller interfaces with the aim that the clients should only think about the methods that are of interest to them.
https://dotnettutorials.net/lesson/interface-segregation-principle/
4. Dependency inversion Principle:
1. The Dependency Inversion Principle (DIP) states that high-level modules/classes should not depend on low-level modules/classes. Both should depend upon abstractions.
2. Secondly, abstractions should not depend upon details. Details should depend upon abstractions.
No comments:
Post a Comment