CHAPTER 9
Here in Chapter 10, I will conclude our look at SOLID, but if you want to discuss these topics further, feel free to contact me. Otherwise, make a pull request to the repository, or send me your solutions and I will push them to the SOLID Succinctly repository.
Let’s recap what we have examined so far.
We started with a look at the importance of SOLID principles in our code and programs.
We discussed:
Please refer to the Bitbucket source code repository for exercises.
1. Can we implement SOLID principles in an existing project, and if yes, how so?
Yes, we can implement SOLID principles in our existing projects. Keep in mind that SOLID principles are not tools; these are only guidelines that tell us how to write SOLID code.
Here are few things I find useful for cleaning or refactoring an existing code. Thanks to Stack Overflow and Aaron Daniels for http://stackoverflow.com/questions/783974/how-to-implement-solid-principles-into-an-existing-project.
Single Responsibility Principle
As we now know, a single class should have a single responsibility. So, while you’re implementing SOLID in your existing project, please take a deeper look into your classes to see if they obey or can be modified to follow SRP. To do so, I generally follow these steps:
Note: The above points are not rules of thumb or principles for refactoring, these come from my own practices—you can compile steps that work for you.
Open-Closed Principle
We know that our classes should not allow changes but instead should be open for extension.
In order to obey OCP, check your existing code and try to keep your members and methods virtual or abstract, if feasible. If you want to extend any functionality at some point, make sure you can do so easily later on.
Liskov Substitution Principle
In my view, in order to implement this principle, you should check and implement inheritance in the way that best suits your existing code.
Interface Segregation Principle
To reiterate the definition:
“No client should be forced to implement methods it does not use, and large interfaces should be broken into small and more specific interfaces.”
Get into a deep check of your existing code and identify the interfaces in which you are providing too many responsibilities to classes (that are implementing these interfaces). Now, try to refactor these interfaces by diving into various interfaces. I suggest going to the code examples discussed in our chapter on the Interface Segregation Principle.
Dependency Inversion Principle
In order to implement this pattern, think about a scenario in which we are trying to use the functionality but without giving full details. I suggest you go through our discussion on DIP.
2. I am aware that Repository Pattern violates SOLID principles, but which principles does it violate and how so?
Yes, Repository Pattern violates SOLID principles. In order to discover which principles and how so, we need to revisit the Repository Pattern (it’s beyond the scope of this e-book, so I am using a few simple code examples).
Consider the following repository in Code Listing 47 (I am not considering generic repository).
Code Listing 47
public interface IServerDataRepository { void Add(ServerData data); bool Save(ServerData data); bool Update(ServerData data); ServerData GetBy(int id); ServerData FindSpecialRecById(int splid); bool DeleteById(int id); IEnumerable<ServerData> GetServerData(); } |
Here, we have interface IServerDataRepository, and as is evident by its name, it’s a repository of ServerData and contains the following methods:
In Code Listing 48, we have two classes, ServerData and SpecialServerData, that implement the interface IServerDataRepository.
Code Listing 48
using System.Collections.Generic; public class ServerData : IServerDataRepository { public void Add(ServerData data) { //Code implementation } public bool Save(ServerData data) { //Code implementation } public bool Update(ServerData data) { //Code implementation } public ServerData GetBy(int id) { //Code implementation } public ServerData FindSpecialRecById(int splid) { //Code implementation } public bool DeleteById(int id) { //Code implementation } public IEnumerable<ServerData> GetServerData() { //Code implementation } } |
Look closely at Code Listing 48 and find which SOLID principle the code is breaking. Yes, it’s breaking SRP. Our class is doing many things—it’s meant to update records, delete records, fetch records, and more—but it should have only a single responsibility. Imagine the implementation of each method. It’s dirty code in which we are making a mess of our code in a single place. It reminds me of my student days of coding, but now it’s time to make our code mature. That’s how we need to think as we address this class—simply that it is violating the Single Responsibility Principle.
Code Listing 49
using System.Collections.Generic; public class SpecialServerData : IServerDataRepository { //We are supposed to implement only this method. public ServerData FindSpecialRecById(int splid) { //Code implementation } //Unfortunately we have to implement them all. public void Add(ServerData data) { //Code implementation } public bool Save(ServerData data) { //Code implementation } public bool Update(ServerData data) { //Code implementation } public ServerData GetBy(int id) { //Code implementation } public bool DeleteById(int id) { //Code implementation } public IEnumerable<ServerData> GetServerData() { //Code implementation } } |
In the Code Listing 48 implementation, we require only one method implementation, but we have to implement all the methods, so we are violating ISP.
As an exercise, look at Code Listing 49 and try to find more points where a SOLID principle is being violated.
3. As a solution architect, what priorities do you keep in mind while implementing SOLID principles in your new solution?
There are many ways to answer this important question. From my vantage point as a solution architect, the first thing I must provide is the solution of a real-time design or a problem, etc. The solution can depend on or be independent from language. For example, a solution should work similarly for Java and C# code.
Here are a few recommended points I keep in mind while writing new programs:
4. Are SOLID principles good for writing code that is easily testable?
Yes. SOLID principles are indeed good for writing a code that can be easily tested. Consider the five principles and try to map how easy they would be to test:
Single Responsibility Principle
In a case in which a class is responsible for saving data, the implementation of the Save method should be abstracted somewhere in a business class to implement business logics.
Therefore, using SRP, we can easily test our class and business logics by writing only two test cases—one to test Save method and one to test business logics.
Open-Closed Principle
Refer to Code Listing 15, in which ValidateData class implements the IValidator interface and validates the ServerData with SourceData.
Using OCP, we can write less code to test, and we created mock data and test validations in our Code Listing 15 example.
Liskov Substitution Principle
In Code Listing 27, we used the IValidatorLoader interface, and we can use the IsValid() method while using any validators such as TypeValidator, IPValidator, etc., without knowing which validator class we are using.
By following LSP, we can write tests for mocked objects, test fake data, and verify it.
Interface Segregation Principle
With the use of ISP, in which we have smaller methods, we can more easily use tests to mock our methods.
Dependency Inversion Principle
Finally, by following DIP, we are writing good code, which makes it easily testable code. As with using ISP, we can easily mock an interface when we use DIP.
1. Which SOLID Principle is violated in Code Listing 50?
Code Listing 50
using System; public abstract class Notify { public abstract void NotifyClient(); } class OnPremisesClient : Notify { public override void NotifyClient() { Console.WriteLine("You're getting these notifications because you opted...."); } } class CloudClient : Notify { public override void NotifyClient() { Console.WriteLine("You're getting these notifications because you opted...."); if (IsOnPremisesToo) NotifyClientAsOnPremisesClient(); } public void NotifyClientAsOnPremisesClient() { Console.WriteLine("Awesome! You are also using On premises services..."); } public bool IsOnPremisesToo { get; set; } } |
The code in Code Listing 51 implements the preceding classes and code.
Code Listing 51
class Program { static void Main(string[] args) { var premisesClient = new OnPremisesClient(); var cloudClient = new CloudClient(); ProcessNotifications(new List<Notify> { premisesClient, cloudClient }); } private static void ProcessNotifications(List<Notify> list) { throw new NotImplementedException(); } static void HandleItems(IEnumerable<Notify> notifications) { foreach (var notification in notifications) { if (notification is CloudClient) { var cloudClient = notification as CloudClient; cloudClient.IsOnPremisesToo = true; } notification.NotifyClient(); } } } |
The code snippets in both Code Listings 50 and 51 are violating SOLID principles. Let’s discuss them one by one:
Single Responsibility Principle
The Notify class uses Console.WriteLine and, as per class, the main purpose of this notification is not clear.
Class Notification should have one and only one method. The corrected code that follows SRP would look like Code Listing 52.
Code Listing 52
public void SendAll() { foreach (var notificationProvider in _providerList) { notificationProvider.Process(); } } |
Liskov Substitute Principle
We should use some kind of base class, making sure to use Is-substitute for the relationship instead of Is-A (which is currently being used in the code).
The corrected code that follows LSP would look like Code Listing 53.
Code Listing 53
public class OnPremiseProvider : INotify { public void Process() { //This should be used more than just a message or notification. //Assuming there are few business rules for this particular provider //process those rules and change code accordingly. Console.WriteLine("You're getting these notifications because you opted for OnPremise Notifications...."); } } |
We have interface INotify and now, whenever we make a call from client, we use INotify instead of OnPremisesProvider, which will look something like Code Listing 54.
Code Listing 54
INotify onPremiseProvider = new OnPremiseProvider(); |
Dependency Inversion Principle
Code Listing 54 clearly violating DIP, as it’s dependent on Notify class. Instead of client, we should make changes to pass messages from the notifier itself.
With the implementation of SendAll method, our code is no longer dependent on client implementations.
With the new code changes, I have also refactored the code. Code Listing 55 shows the code when obeying SOLID Principles.
Code Listing 55
public class Notification { private readonly IEnumerable<IProcess> _providerList; public Notification(IEnumerable<IProcess> providerList) { _providerList = providerList; } public void SendAll() { foreach (var notificationProvider in _providerList) { notificationProvider.Process(); } } } public interface IProcess { void Process(); } public class OnPremiseProvider : IProcess { public void Process() { //This should be used more than just a message or notification. //Assuming there are few business rules for this particular provider //process those rules and change code accordingly. Console.WriteLine("You're getting these notifications because you opted for OnPremise Notifications...."); } } public class CloudNotifier : IProcess { public void Process() { //This should be used more than just a message or notification. //Assuming there are few business rules for this particular provider //process those rules and change code accordingly. Console.WriteLine("You're getting these notifications because you opted for Cloud Notifications...."); } } public class GalaxyNotifier : IProcess { public void Process() { //This should be used more than just a message or notification. //Assuming there are few business rules for this particular provider //process those rules and change code accordingly. Console.WriteLine("You're getting these notifications because you opted for Galaxy Notifications...."); } } |
In the new code, we have renamed NotifyClient method to Process because this will process our notifications for client. Every notifier implements the IProcess interface. Now, at client side there is no need to take care of notifications per provider, and our Notification class will automatically take care of these notifications. So, our client code would look Code Listing 56.
Code Listing 56
class Program { static void Main(string[] args) { var notification = new Notification(new List<IProcess> { new CloudNotifier(), new OnPremiseProvider(), new GalaxyNotifier() }); notification.SendAll(); Console.ReadLine(); } } |
There are many ways to implement the code, but the main consideration is that we follow SOLID principles.
Note: Complete source code is available on the Bitbucket repository under Chapter 10. You can also see the working code using this fiddle: https://dotnetfiddle.net/vXg3Lq .
2. Implement the SOLID principle you found for Q.3 code.
In order to implement this yourself, first figure out the violated SOLID principle, then rewrite the entire code. I would love to see your new code—you can share by creating a pull request on the source code repository at Bitbucket, or you can write to the email address I’ve given in the About the Author section.
Here is a list of comprehension questions.
1. What are SOLID principles? Explain with examples.
2. Explain the importance of the Single Responsibility Principle, using an example.
3. What SOLID principles are you violating while implementing Repository Pattern?
4. What is meant by the principle of package and namespace coupling?
5. What is meant by the principle of package and namespace cohesion?
6. As a .NET developer, how do you find SOLID principles in asp.net or MVC projects (code/folder structure in Visual Studio)?
7. What is the importance of refactoring while you’re working on legacy projects and while you’re working on new projects?
8. Create a small utility to grab configurations key/values from a config file (these files might have an extension of .config, .txt, .ini, or .json) and have a key value pair in the format of <key=”” value=”” otherinfo=”” /> by using SOLID principles.
9. Share your experience from your first SOLID refactoring.
10. Give an example for OCP versus LSP.
Note: LSP is an extension of the Open-Closed Principle.