left-icon

SOLID Principles Succinctly®
by Gaurav Kumar Arora

Previous
Chapter

of
A
A
A

CHAPTER 9

Conclusion

Conclusion


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.

The importance of SOLID

We started with a look at the importance of SOLID principles in our code and programs.

We discussed:

  • Why SOLID?—We concluded that SOLID principles provide us with a way to write neat, clean code base that is readable by humans.
  • Checklist—We concluded with a checklist of points to help determine whether or not our code is dirty code:
  • Is the code implementing design patterns?
  • Is the code tightly coupled?
  • Is the code testable?
  • Is the code human readable?
  • Is the code duplicated?
  • Is the code too lengthy to understand?
  • Should I care about SOLID?We concluded that if we want to make our code readable, robust, and clean, we must care about SOLID.
  • Starting to learn SOLID—We looked at things that commonly puzzle people when they start to learn SOLID principles.
  • Single Responsibility Principle—We can define Single Responsibility Principle with: “A class should be designed for a single responsibility. The responsibility of this class should be completely encapsulated by the class.”
  • Open-Closed Principle—We can use Wikipedia’s definition for OCP: “Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”
  • Liskov Substitution Principle—We concluded that this principle can be summarized as “a parent should be easily replaced by the child object without affecting the correctness of the program.”
  • Interface Segregation Principle—We can describe this with: “No client should be forced to implement methods it does not use, and large interfaces should be broken into small and more specific interfaces.” We also dipped into the High Cohesion Principle of GRASP, which refers to how operations of elements are functionally related.
  • Dependency Inversion Principle—We noted this is related to decoupling and that DIP states: “High-level modules should not depend on low-level modules. Both should depend on abstractions.”

Please refer to the Bitbucket source code repository for exercises.

Likely questions about SOLID

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:

  1. Check what your class is written for.
  2. See if your class is doing multiple things at once.
  3. Think about whether or not there are any scenarios or reasons that could force your class to change.
  4. Start refactoring.
  5. Make small chunks of your class functionalities. You can break one class into many classes in order to serve SRP.

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:

  • Add–Inserts new ServerData records.
  • Save–Persists ServerData records and returns true/false.
  • Update–Updates the existing ServerData record and returns true/false.
  • GetBy–Fetches ServerData record by ServerData ID.
  • FindSpecialRecById–Fetches ServerData record by ServerData special ID (as can be predicted from its name, this seems to be a special method).
  • DeleteById–Removes ServerData record by ID and returns true/false. (It isn’t clear from the method name if this method deletes records physically or just marks records as deleted).
  • GetServerData–Returns a collection of type ServerData. (It looks like a heavy operation because it fetches all records. If you have 30 million records in a database, is this a good method?)

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:

  • Write a class which deals only with one operation at a time—if my class is meant to show data, it should not do save data, etc. (SRP).
  • Write classes in a manner so that there will be no reason to later change the functionality of the class (OCP).
  • Abstract code so that the client is able to use a derived object instead of an instance of a class defining the parent type (LSP).
  • Create smaller interfaces with minimum methods during segregation (ISP).
  • Keep loose coupling in mind while writing software/an application,(DIP).

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.

Test your understanding

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.

Test questions

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.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.