Interface Segregation Principle
Introduction
The interface segregation principle states that a class should not be forced to implement interfaces it does not use. The principle is intended to prevent classes from becoming too large and unwieldy by breaking them down into smaller, more manageable pieces. While a little more subjective than some of the other SOLID principles, the Interface Segregation Principle is still an important guideline to keep in mind when designing software.
Example
Let's start with a simple interface describing a printer:
interface PrinterTasks {
fun print(printContent: String)
fun scan(scanContent: String)
fun fax(faxContent: String)
fun printDuplex(printContent: String)
}
class LaserPrinter : PrinterTasks {
override fun print(printContent: String) { }
override fun scan(scanContent: String) { }
override fun fax(faxContent: String) { }
override fun printDuplex(printContent: String) { }
}
This simple approach work just fine if every printer can perform all of these tasks, and every client wants all of them. However, what if we have a printer that can only print and scan? In this case, we would have to implement the fax
and printDuplex
methods with empty bodies, or that throw exceptions, which is not ideal.
class InkJetPrinter : PrinterTasks {
override fun print(printContent: String) { }
override fun scan(scanContent: String) { }
override fun fax(faxContent: String) {
throw UnsupportedOperationException("This printer does not support faxing")
}
override fun printDuplex(printContent: String) {
throw UnsupportedOperationException("This printer does not support duplex printing")
}
}
A better approach would be to break the PrinterTasks
interface into smaller, more focused interfaces:
interface Print {
fun print(printContent: String)
}
interface Scan {
fun scan(scanContent: String)
}
interface Fax {
fun fax(faxContent: String)
}
interface PrintDuplex {
fun printDuplex(printContent: String)
}
class LaserPrinter : Print, Scan, Fax, PrintDuplex {
override fun print(printContent: String) { }
override fun scan(scanContent: String) { }
override fun fax(faxContent: String) { }
override fun printDuplex(printContent: String) { }
}
class InkJetPrinter : Print, Scan {
override fun print(printContent: String) { }
override fun scan(scanContent: String) { }
}
Now, each printer class can implement only the interfaces that are relevant to it, and clients can interact with the printer using only the methods they need. This has the added benefit of limiting the scope of changes when new features are added or existing features are modified. For example, if we need to change the function signature of the scan
method, only clients that specifically use the Scan
interface will be affected.
Conclusion
The interface segregation principle is an important guideline to keep in mind when designing software, but isn't as commonly used as some of the other SOLID principles. By breaking interfaces down into smaller, more focused pieces, we can create more flexible and maintainable code that is easier to understand and modify. This can help prevent classes from becoming too large and unwieldy, and make it easier to add new features or modify existing ones without affecting unrelated parts of the codebase.