The Interface Substitution Principle
Tailor Your Interfaces to the Client’s Specific Requirements
The Interface Segregation Principle (ISP) in the Modern Developer’s Design Series supports the
Single Responsibility Principle (SRP).
The Interface Segregation Principle States:
Clients of Interfaces Should Not Be Forced
… To Implement Interface Contracts
…… They Do Not Require
Compliance with ISP creates smaller, responsibility centric, Interfaces that a Class or Struct can implement.
By creating a contract that contains only the responsibility of the implementing Software Entity a much clearer understanding of the underlying intent of the Software Entity is achieved.
The Fat Class Solution Using the ISP
The Interface Segregation Principle is a powerful tool for helping a developer to not continue violation SRP when confronted with a “Fat Class“, in a Brownfield project, that violates SRP.
A Fat Class is a Class or a Struct File
… That is being used as a Code “Filing Cabinet”
…… Rather than a Code “File”
When a development team has many developers that are working on related functionality many times they will simply copy and paste similar methods within a class without regard for the initially defined responsibility.
This, over time, creates a violation of SRP and becomes a Fat Class that is acting as a repository of unrelated methods.
This Filing Cabinet Class
… Must be Segregated
…… Into Its Related Responsibilities
The problem is that most of the time when this is allowed to happen the development process has not implemented a Code Review process or Unit and Integration testing.
Without an oversight team and testing suites to mange regressive bug issues, refactoring of this code could be problematic.
A Class with Thousands of Lines of Code
… Becomes Unmanageable over Time
…… But is at Greater Risk if Refactored all at Once
When a develop is given a task to add to this Fat Class the developer has a choice:
Do I Become Part of the Problem
Or Do I become Part of the Solution?
Being Part of the Solution
- Create a Single Interface for the Fat Class – Using the IDE tool or a tool such as ReSharper extract all of the Method Signatures into a Single Interface
- Implement the Interface in the Fat Class – The implementation, with a good compile, will validate that the Fat Interface is correct
- Create All Single Responsibility Interfaces – Group the Contract Signatures into related responsibility groups as Segregated Interfaces
- Replace the Single Interface with the New Interface Collection – String all of the Interfaces as the replacement for the Fat Interface
- Successfully Compile the Assembly – Verify that all of the Interface Contracts were migrated to the new Interface collection. Use the IDE, or a tool such as ReSharper, to confirm that each method is fulfilling a contract in its related Interface by inspecting the method signature lines for each method
- Create a New Concrete Class that Implements Your Interface Responsibility – The new concrete becomes your solution to the problem. Place your new code into this class and use it as your code solution, not a new method in the violated Fat Class
- Verify that All Usages function as Expected – Make sure that you have not introduced an bugs into the solution
The beauty of this solution is that as long as the Fat Class compiles with the Interface collection stringed together, no legacy code will break. You have created abstractions for all future developers to create new concretes as part of the solution, as you did.
You MUST document your changes … Into a Developer Common Knowledge Repository …… And pass the information Location to ALL Developers
Code review must not allow any future violations to this Class
It is possible to fix this class at this time if you have access to all assemblies that consume the methods. Create a separate Concrete Class for each Interface and point all usages to the new classes. Take the Fat Class out of the project and fix any compilation errors. Always comply with the Boy Scout Principle Always leave a Software Entity in Better Shape … Than it was in when you entered the Legacy Code Environment
The Interface Segregation Principle
The primary concept of ISP is to create Class and Struct contracts that comply with the Single Responsibility Principle (SRP) by defining only the signatures that the Class’s responsibility requires.
If an Interface Violates SRP
… Then ALL Implementations of that Interface
…… will be Forced to Violate SRP
An implementing Class should require that its abstraction only contract the concrete implementations that it knows it needs to support the responsibility of its Client method calls
One of the most expensive Code Smells in development is the violation of SRP through Fat Classes.
Fat classes are very hard to understand in a reasonable amount of time. They tend to be “Responsibility Repositories” that reside in “Filing Cabinet” code files.
They are generally the results of many developers working on code without a clear understanding of the overall intent of the code file.
The primary justification of ISP is to create a contract vehicle that forces a Class or Struct to comply with the Single Responsibility Principle by defining Software Entities that support a single purpose.
Compliance with ISP
… Forces the Implementing Class to Comply with SRP
The Interface Segregation Principle provides a “Bridge to Compliance”
for refactoring of violations within “Fat Classes”
- Contract Entities that have no implementation or use within a Class or Struct
- A variation violation is in Class Inheritance where a Base Class violates Liskov Substitution Principle and requires a derived class to have knowledge of Properties and Methods it does not require.
If a Class has to use a #region Tag to Hide Contracts … Then ISP has been Violated
- Not Implemented Exceptions within a Method – Default implementation for IDE Interface compliance support
- Empty Methods – Unused signatures.
- The Use of #regions to Hide the Unimplemented Contracts – Reduction of Code Noise using Regions to hide the ISP violation.
This code example will demonstrate the “Fat Class” violation in our sample Print Data Console application.
The legacy code version will violate Single Responsibility Principle in the Console application by showing a class that has four responsibilities contracted to a “Fat Interface” that forces the class to violate SRP.
The Console Display Code:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System; using ISP.PrintDocsFatClass.Legacy.Dependencies; using SRP.PrintDocs.Refactored.Enums; namespace SRP.PrintDocs.Refactored { public class PrintData { private static DataPrintingFatClass _dataPrinting { get; set; } static PrintData() { _dataPrinting = new DataPrintingFatClass(); } static void Main() { var printFormat = _dataPrinting.FormatData(PrintFormat.Landscape); var printDevice = _dataPrinting.SelectOutputDevice(OutputDevices.Console); var printData = _dataPrinting.GetDataFromFile(); _dataPrinting.PrintDataToOutputDevice(printFormat, printDevice, printData); } } } |
The Fat Interface Code for the DataPrintingFatClass :
|
1 2 3 4 5 6 7 8 9 10 11 12 |
using SRP.PrintDocs.Refactored.Enums; namespace ISP.PrintDocsFatClass.Legacy.Interfaces { public interface IDataPrintingFatInterface { string FormatData(PrintFormat format); string GetDataFromFile(); string SelectOutputDevice(OutputDevices outputDevice); void PrintDataToOutputDevice(string printFormat, string printDevice, string printData); } } |
The Fat Class Code Implementing the Fat Interface IDataPrintingFatInterface :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
using System; using ISP.PrintDocsFatClass.Legacy.Interfaces; using SRP.PrintDocs.Refactored.Enums; using SRP.PrintDocs.Refactored.ModelDTOs; namespace ISP.PrintDocsFatClass.Legacy.Dependencies { public class DataPrintingFatClass : IDataPrintingFatInterface { public string FormatData(PrintFormat format) { var formattingTypes = new SamplePrintFormattingTypesDTO(); return format == PrintFormat.Landscape ? formattingTypes.PrintFormatters["Landscape"] : formattingTypes.PrintFormatters["Portrait"]; } public string GetDataFromFile() { return new SamplePrintDataDTO().PrintData; } public string SelectOutputDevice(OutputDevices outputDevice) { switch (outputDevice) { case OutputDevices.Printer: return "I am Printing to the Printer"; case OutputDevices.Console: return "I am Printing to the Console"; case OutputDevices.XmlFile: return "I am Printing to an XmlFile"; default: return "I can not print as I do not have a valid device"; } } public void PrintDataToOutputDevice(string printFormat, string printDevice, string printData) { Console.WriteLine(""); Console.WriteLine("The ISP Fat Class Print Data Demo:n"); Console.WriteLine("tSelected Printer Format: {0}n", printFormat); Console.WriteLine("tSelected Printer Device: {0}n", printDevice); Console.WriteLine("tRefactored Print Data: {0}n", printData); Console.WriteLine("All Demo Data Display Complete"); Console.ReadKey(); } } } |
The Class Diagram for the Legacy Violation:
The Console Display for the Legacy Violation:
This code is a simple example of a Class that is being used as a Class Method “Filing Cabinet“.
The DataPrintingFatClass has four responsibilities that are being defined by the IDataPrintingFatInterface.
This forces the implementing Class to violate SRP
The refactored version of the console application above with solve the violation by extracting an Interface from the “Fat Class” and creating Interfaces that have a Single Responsibility.
The “Fat Class” is then refactored into four single responsibility classes that implement the new single responsibility Interfaces.
The Console Display Code:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using SRP.PrintDocs.Refactored.Enums; using SRP.PrintDocs.Refactored.ViewModels; namespace SRP.PrintDocs.Refactored { public class PrintData { private static PrinterFormatter _printerFormatter { get; set; } private static PrinterOutputDevice _printerOutputDevice { get; set; } private static PrinterOutputData _printerOutputData { get; set; } private static DataPrinting _dataPrinting { get; set; } static PrintData() { _printerFormatter = new PrinterFormatter(); _printerOutputDevice = new PrinterOutputDevice(); _printerOutputData = new PrinterOutputData(); _dataPrinting = new DataPrinting(); } static void Main() { var printFormat = _printerFormatter.FormatData(PrintFormat.Landscape); var printDevice = _printerOutputDevice.SelectOutputDevice(OutputDevices.Console); var printData = _printerOutputData.GetDataFromFile(); _dataPrinting.PrintDataToOutputDevice(printFormat, printDevice, printData); } } } |
The Fat Interface Code refactored into Four Interfaces:
|
1 2 3 4 5 6 7 |
namespace SRP.PrintDocs.Refactored { public interface IDataPrinting { void PrintDataToOutputDevice(string printFormat, string printDevice, string printData); } } |
|
1 2 3 4 5 6 7 8 9 |
using SRP.PrintDocs.Refactored.Enums; namespace SRP.PrintDocs.Refactored.ViewModels { public interface IPrinterFormatter { string FormatData(PrintFormat format); } } |
|
1 2 3 4 5 6 7 |
namespace SRP.PrintDocs.Refactored.ViewModels { public interface IPrinterOutputData { string GetDataFromFile(); } } |
|
1 2 3 4 5 6 7 8 9 |
using SRP.PrintDocs.Refactored.Enums; namespace SRP.PrintDocs.Refactored.ViewModels { public interface IPrinterOutputDevice { string SelectOutputDevice(OutputDevices outputDevice); } } |
The four new classes that implement the ISP Interfaces:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using System; namespace SRP.PrintDocs.Refactored { public class DataPrinting : IDataPrinting { public void PrintDataToOutputDevice(string printFormat, string printDevice, string printData) { Console.WriteLine(""); Console.WriteLine("The ISP Refactored Fat Class Demo:n"); Console.WriteLine("tSelected Printer Format: {0}n", printFormat); Console.WriteLine("tSelected Printer Device: {0}n", printDevice); Console.WriteLine("tRefactored Print Data: {0}n", printData); Console.WriteLine("All Demo Data Display Complete"); Console.ReadKey(); } } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using SRP.PrintDocs.Refactored.Enums; using SRP.PrintDocs.Refactored.ModelDTOs; namespace SRP.PrintDocs.Refactored.ViewModels { public class PrinterFormatter : IPrinterFormatter { public string FormatData(PrintFormat format) { var formattingTypes = new SamplePrintFormattingTypesDTO(); return format == PrintFormat.Landscape ? formattingTypes.PrintFormatters["Landscape"] : formattingTypes.PrintFormatters["Portrait"]; } } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
using SRP.PrintDocs.Refactored.ModelDTOs; namespace SRP.PrintDocs.Refactored.ViewModels { public class PrinterOutputData : IPrinterOutputData { public string GetDataFromFile() { return new SamplePrintDataDTO().PrintData; } } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
using SRP.PrintDocs.Refactored.Enums; namespace SRP.PrintDocs.Refactored.ViewModels { public class PrinterOutputDevice : IPrinterOutputDevice { public string SelectOutputDevice(OutputDevices outputDevice) { switch (outputDevice) { case OutputDevices.Printer: return "I am Printing to the Printer"; case OutputDevices.Console: return "I am Printing to the Console"; case OutputDevices.XmlFile: return "I am Printing to an XmlFile"; default: return "I can not print as I do not have a valid device"; } } } } |
The Class Diagram for the ISP compliance solution:
The console display for the compliant code base:
The refactored solution supports SRP and ISP. It is also extensible through its interfaces.
The Benefits of Compliance with the ISP Principle:
-
Supports the Single Responsibility Principle
-
Supports Better implementation of Class Inheritance by Identifying “Is A” Violations
-
A Powerful Tool in Refactoring Brownfield Fat Classes
-
Allows Developers to be part of the Solution when forced to use Classes that Violate SRP
-
Supports designing to Abstractions rather than Concrete implementations
-
Aids to a Clearer Understanding of the Underlying Intent of the Class or Struct
Assemblies that Comply with ISP have a
Lowered Total Cost of Ownership for the Client
as they Support a Better Understanding of the Code
for Maintenance Developers
The Next Principle Design Series Post:
The Dependency Inversion Principle
… Would You Solder a Lamp Directly
…… Into the Electrical Wiring in a Wall?
Latest posts by Brad Huett (see all)
- DevOps: A Bridge to Your DevOps Culture - March 25, 2016
- Embracing Test Driven Development (TDD) - March 25, 2016
- DevOps: Delivering Agile Projects - March 25, 2016






