Wednesday, December 31, 2014

Single Responsibility Principle

A brief Intro

The SOLID Principles series describes the Single Responsibility Principle (SRP). The SRP states that each class or similar unit of code should have one responsibility only and, therefore, only one reason to change.


The Principle

The Single Responsibility Principle (SRP) states that there should never be more than one reason for a class to change. This means that every class, or similar structure, in your code should have only one job to do. Everything in the class should be related to that single purpose. It does not mean that your classes should only contain one method or property. There may be many members as long as they relate to the single responsibility. It may be that when the one reason to change occurs, multiple members of the class may need modification. It may also be that multiple classes will require updates.

Application of the SRP can change your code considerably. One key change is that the classes in your projects become smaller and cleaner. The number of classes present in a solution may increase accordingly so it is important to organise them well using namespaces and project folders. The creation of classes that are tightly focussed on a single purpose leads to code that is simpler to understand and maintain.

A further benefit of having small, cohesive classes is that the chances of a class containing bugs is lowered. This reduces the need for changes so the code is less fragile. As the classes perform only one duty, multiple classes will work together to achieve larger tasks. Along with the other principles this permits looser coupling. It can also make it easier to modify the overall software, either by extending existing classes or introducing new, interchangeable versions.
Example Code
To demonstrate the application of the SRP, we can consider an example C# class that violates it and explain how the class can be refactored to comply with the principle:
  1. public class OxygenMeter
  2. {
  3. public double OxygenSaturation { get; set; }
  4. public void ReadOxygenLevel()
  5. {
  6. using (MeterStream ms = new MeterStream("O2"))
  7. {
  8. int raw = ms.ReadByte();
  9. OxygenSaturation = (double)raw / 255 * 100;
  10. }
  11. }
  12. public bool OxygenLow()
  13. {
  14. return OxygenSaturation <= 75;
  15. }
  16. public void ShowLowOxygenAlert()
  17. {
  18. Console.WriteLine("Oxygen low ({0:F1}%)", OxygenSaturation);
  19. }
  20. }


code above is a class that communicates with a hardware device to monitor the oxygen levels in some water. The class includes a method named "ReadOxygenLevel" that retrieves a value from a stream generated by the oxygen monitoring hardware. It converts the value to a percentage and stores it in the OxygenSaturation property. The second method, "OxygenLow", checks the oxygen saturation to ensure that it exceeds the minimum level of 75%. The "ShowLowOxygenAlert" shows a warning that contains the current saturation value.


There are at least three reasons for change within the OxygenMeter class. If the oxygen monitoring hardware is replaced the ReadOxygenLevel method will need to be updated. If the process for determining low oxygen is changed, perhaps to include a temperature variable, the class will need updating. Finally, if the alerting requirements become more sophisticated than outputting text to the console, the ShowLowOxygenAlert method will need to be rewritten.

Refactored Code


To refactor the code we will separate the functionality into three classes. The first is the OxygenMeter class. This retains the OxygenSaturation property and the ReadOxygenLevel method. You could decide to split these members into separate classes. In this case we will keep them together as they are closely related. The other methods are removed so that the only reason for change is replacement of the monitoring hardware.
The second class is named "OxygenSaturationChecker". This class includes a single method that compares the oxygen level with the minimum acceptable value. The method is the same as the original version except for the addition of a parameter that injects an OxygenMeter object containing the saturation level to test. The only reason for the class to change is if the test process is changed.
The final class is named "OxygenAlerter". This displays an alert that includes the current oxygen saturation level. Again, the OxygenMeter dependency is injected. The one reason for the class to change is if the alerting system is updated.
NB: The refactored example code below breaks other SOLID principles in order that the application of the SRP is visible. Further refactoring of this example is necessary to achieve compliance with the other four principles.

  1. public class OxygenMeter
  2. {
  3. public double OxygenSaturation { get; set; }
  4. public void ReadOxygenLevel()
  5. {
  6. using (MeterStream ms = new MeterStream("O2"))
  7. {
  8. int raw = ms.ReadByte();
  9. OxygenSaturation = (double)raw / 255 * 100;
  10. }
  11. }
  12. }


  1. public class OxygenSaturationChecker
  2. {
  3. public bool OxygenLow(OxygenMeter meter)
  4. {
  5. return meter.OxygenSaturation <= 75;
  6. }
  7. }


  1. public class OxygenAlerter
  2. {
  3. public void ShowLowOxygenAlert(OxygenMeter meter)
  4. {
  5. Console.WriteLine("Oxygen low ({0:F1}%)", meter.OxygenSaturation);
  6. }
  7. }

No comments:

Post a Comment