Do you really want to develop the extensible, flexible and mobile code while working on the project in your organization. If yes then you should be aware of the SOLID design principles. In this article I will cover SOLID design principles with the help of practical and real world examples which we can relate to our own application.
To learn C# and face interview with confidence I have recommended some very good books. These books can really help you to understand .NET and C#. Below is the link:
Before going further have you ever encountered this kind of conversation with your colleague in your organization
Developer 1: I am really worried.
Developer 2: What Happened
Developer 1: Whenever I fix a bug in the system, some other functionality brakes which I have not at all touched.
Developer 2: hmm..Have you used SOLID principles while designing and developing the application ?
Developer 1: No, what are they?
SOLID Principles
Some important point about SOLID design principles.
- Generally speaking, SOLID state of matter can be brittle. But while talking about code we are not aiming for brittleness of code.
- As far as software designing is concerned, SOLID should be used where code can change shape.
- SOLID can come to rescue when all the requirements are not upfront while developing code.
- SOLID is not a framework
- SOLID principle is not a library.It is not technology bound. It is not pattern.
- Most of the code written today, though they are written in object oriented way but they are not object oriented and more or less procedural. Writing code in OO language is not a guarantee that the code will be OO.
- But we can make our code OO by using solid principles.We can more productive by using SOLID.
- Code is more maintainable and understandable.When code suffer from design smell then SOLID is answer.
How SOLID principles Solves read world Application Problems
Some of the design smell I which I want to discuss here are.
- Rigidity – The design is difficult to change. Without proper use of abstraction and interface the design becomes very rigid. For each and every small change in the functionality we have to test whole logic from start.
- Fragility – Design is easy to break. As discussed with a small change there are very high chances of the whole design going for a toss.
- Immobility – The design is difficult to reuse. Or rather we cannot easily extend the current design.
- Viscosity – It is difficult to do the right thing.
- Needless Complexity – Over design.
Following are the five SOLID design principles which we should be ware of
S – Single responsibility principle(SRP)
O – Open closed principle(OCP)
L – Liskov Substitution principle(LSP). How polymorphism should work.
I – Interface segregation Principle(ISP). How interface should be designed.
D- Dependency inversion principle(DIP) which describes principle between abstraction and concrete types.
There is no built-in order to these principles. Just because Single responsibility come first does not mean that it is more important than any other principle in C#. All of the parts of the SOLID are equally important to be implemented in the code. It is like that just because it is a nice acronym. As seen in the below figure all the five principles are related to each other.
Robert C. martin the inventor of the Design principle initially had only three principles designed. But later someone added two more principle and it came out to be a nice acronym.
Apart from the order, one more thing which is important is the relationship among all these principle.
Single Responsibility Principle
- A class should have only single responsibility. A single reason to change.
- Based on separation of concerns.
- Vary each concern independently of other concern.
- Each class should be designed to do one thing and it should do it well.
- The size of the classes become shorter. This makes code easily understandable.
I will discuss the example of the single responsible principle along with the Open closed principle next.
Open Closed Principle
- Writing something very specific to solve some specific problem can lead to writing same code multiple time which in turn results in code duplication
- Code duplication leads to maintainability problem.
- Reused abstraction principle – if we have multiple abstractions which are not being used by the concrete class then it is a sign of poor designing and abstractions.
- Interfaces are not designed but they are discovered as the system grows.
- Start with the concrete behaviour and discover the abstraction as commonality emerges.
- Rule of three – Unless and until we have three places where we think that the code is same, it is generally not advised to abstract out the functionality.
- Open for extensibility but closed for modification. If we have a class which is being used by other clients, that class should be open for extensibility. The only reason to change the class should be to fix a bug in the class.
- We should favor Composition over inheritance which I will discuss in next principles.
- We can make a class open for extensible is by adding virtual keyword to the methods of the class.
Lets see an example of the C# shape class which is not using any of the design principles.
Though there are many short coming in this class. But what is the basic shortcoming which you can see. For every shape added we have to change the GetArea() method as well as the CalculateTotalArea() method. There are many reason for the class to change. It means the class is violating the SRP.
Again whenever we have to add a new shape we have to add a new const as well as add a switch and if statement in both of the methods. It means that the class is not closed for modifications and not easily extend able.
Now lets redesign it. We have created a new system in C# language.
In the above code I have redesigned the class and abstracted out the functions. Now we can see that each and every class is responsible for one and only one functionality. If we want to add a new Shape, we have to only derive from the IShape class and IAreaCalculator will do the needful of calculating the total area of all the shapes.
The above code is following the SRP as said. It also follows the ORP as there is no need to change the AreaCalcualator class whenever a new shape is added. At run time it will take care of all the implementations of the IShape interface.
Liskov Substitution Principle
- It talks about polymorphism.
- Named after barbara liskov, in 70’s
- Derived types must be substituted for base types.
- Client can consume any implementation without changing the correctness of the system
- We can change the behaviour of the system because polymorphism is all about changing the behaviour.
- Correctness of the system can be said as the implementation of the interface A should not crash the application. Similarly implementation of the interface B should also not crash the application. But any implementation of the system is crashing the system that is not the correctness of the system. The implementation is the lowest common denominator when we talk about the correctness of the system. The system can also behave in wrong way if we have a wrong way of the implementation.
Breaking the LSP:
- When we get the NotSupportedException.
- Best example is ICollection<T> in .NET framework. It implements from IEnumerable<T> which means we can enumerate the collection.
- One interesting implementation is ReadOnlyCollection<T>. Once the collection is created it cannot be changed. We cannot add or remove the items from the collection.
- Add , clear and Remove methods throw the NotSupportedExceptions. It can break the correctness of the system because suppose if one part of the client is accepting only the ICollection<T> and it was working fine with List<T> implementation . But if we substitute it with ReadOnlyCollection, it will break for Add, Clear and Remove methods and thus putting the correctness of the system in jeopardy.
- Please check the below C# code. Here Add() method is present for the readOnlyList but it throws the exception. Though the Add method is added implicitly to the ReadOnlyCollection.
- If we have NotSupportedException in the code base, it violates the LSP principle.
- Downcast can also break LSP. Suppose if we are down casting the ICollection to List<T> and performing some operation. In the above scenario the ReadOnlyCollection will throw the exception. Check the below C# code for down casting example.
- Extraction of the interface could violate LSP.
Example.
We have decided to add more shapes to our system. Please check the above C# code. But these shapes are three dimensional. These shapes can have Volume apart from the area. I have added a new method to IShape interface named GetVolume.
I have created a new shape named Cube which derived from IShape. We have a new class named VolumeCalculator which calculates the total volume of all the shapes.
But since we have added new method to IShape interface, we have to implement the method to existing shapes. As I have done for Square, but it is not implemented.
Now if the collection which are sending to the VolumeCalculator contains any of the 2D shapes, it will throw a NotSupportedException for them. This is the violation of LSP in SOLID design principles.
Interface Segregation Principle(ISP)
- ISP is SOLID design principle which is used to help us achieve the LSP by having more granularity.
- Clients should not depend on methods they do not use. As we have see in the previous code example. VolumeCalculator should not depend on the GetVolume methods of the classes.
- Interface are introduced to achieve loose coupling and its not the classes that use the interfaces but clients are the one who use interfaces.
- Clients define the interface and there is no need for an client to define a method in the interface if the client doesn’t need the method in the interface.
- Prefer role based interfaces over header interfaces. Header interfaces are created by abstracting out of the existing classes as we have done in designing previous system.
- If client need to define the interface it only need to define a very few methods in the interface. These are role interface.
- There are many ways by which we can extend the functionality of the class e.g. by inheritance, by using generics or by using extension methods.
- But there are very less way by which we can reduce the methods in the class. Indeed there is only one way and that is ISP.
The above C# code is self explanatory. You can notice how I have segregated the interfaces to achieve ISP.
Dependency Inversion Principle
The important points to keep in mind while developing the solutions which meet the criteria set by DIP are.
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
- Implementation of the DIP can also involve Composite pattern and Decorator pattern which I will discuss in later articles.
Now as per the design of the previous code suppose the VolumeCalculator class is also used to display a message as shown in the below code
Now if some other client comes and they want the message to be displayed in Other Language e.g. French. Is our code extensible? No, not at all. This scenario can be handled by abstracting out the Message() functionality as shown in the below code.
In the above code VolumeCalculator class is determining at run time which instance to use.
Conclusion:
In this article I have discussed all the SOLID principles with practical C# examples. I hope this will help you to have a better understanding of SOLID principles which will eventually help you to code better.
Ref: https://www.dotnetforall.com/solid-design-principles-examples/
Comments
Post a Comment