Interface Segregation Principle
-o0O0o-
“Customers should not rely on methods they don’t use.”
-o0O0o-
“Clients should not be forced to depend upon interfaces that they do not use.”
The interface segregation principle (ISP) states that no code should be forced to depend on methods it does not use.[1] ISP splits interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them. Such shrunken interfaces are also called role interfaces.
Precise application design and correct abstraction is the key behind the Interface Segregation Principle. Though it’ll take more time and effort in the design phase of an application and might increase the code complexity, in the end, we get a flexible code that will be easier to maintain/evolve in the future.
The goal of this principle is to reduce the side effects of using larger interfaces by breaking application interfaces into smaller ones. It’s similar to the Single Responsibility Principle, where each class or interface serves a single purpose. ISP is intended to keep a system decoupled and thus easier to refactor, change, and redeploy.
ISP is one of the five SOLID principles of object-oriented design, similar to the High Cohesion Principle of GRASP. Beyond object-oriented design, ISP is also a key principle in the design of distributed systems in general and microservices in particular. ISP is one of the six IDEALS principles for microservice design.
In summary, the Interface Segregation principle teaches that many specific skinny interfaces are better than a single fat general interface. This allows for more flexibility in coding and will make refactoring and redeployment much easier as everything adheres to a contract.
Code Examples
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
public interface IVehicleMaintenance { void changeWheel(int number); void changeEngineOil(); void lubeChain(); void checkBattery(); } public class CarWorkshop implements IVehicleMaintenace { @Override public void changeWheel(int number) { // … } @Override public void changeEngineOil() { // … } @Override public void lubeChain() { throw new UnsupportedOperationException(“No chain to lube in cars”); } @Override public void checkBattery() { // … } } public class BikeWorkshop implements IVehicleMaintenace { @Override public void changeWheel(int number) { // … } @Override public void changeEngineOil() { throw new UnsupportedOperationException(“No engine oil to change in bikes”); } @Override public void lubeChain() { // … } @Override public void checkBattery() { // only valid for electric bikes.. } } /* * Same happens for all other types of vehicles. * Even for the changeWheel operation, which looks more common, it does not * apply to boats, and it may behave differently in different vehicles with * different number and types of wheels. * * In fact, throwing an UnsupportedOperationException in the CarMaintenance * and BikeMaintenance classes is a violation of the LSP as the superclass * (interface in this case) is not replaceable by the subclasses. * * Moreover, with this solution we would be breaking the Open/Closed Principle * (OCP), which states that a class should be open for extension but closed * for modification. This is so because, in case of adding a new type of * vehicle, we would have to modify VehicleMaintenace to add the new types * of maintenance operations, and then we would have to modify all subclasses * to add the new operations even if they would not apply to the specific * vehicle represented by a particular subclass. * */ //////////////////////////////////////////////////////////////////// /* * A simple solution is to separate the interfaces. That is, to define * the hierarchy at the level of interfaces because interfaces are * not meant to be replaced and then the OCP principle would not apply * but we still would be respecting the ISP principle. In this solution * the clients (CarWorkshop and BikeWorkshop) depend on the respective * interface they need. */ public interface IVehicleMaintenance { public void void cleanExterior(); // Other operations that are common to all vehicles } public interface ICarMaintenance extends IVehicleMaintenace { public void changeWheel(int number); public void changeEngineOil(); public void checkBattery(); // Other operations that are common to all cars } public interface IBikeMaintenance extends IVehicleMaintenace { public void lubeChain(); // Other operations that are common to all bikes } public class CarWorkshop implements ICarMaintenance { public void cleanExterior() { // … } public void changeWheel(int number) { // … } public void changeEngineOil() { // … } public void checkBattery() { // … } // Other operations that are common to all cars } public class BikeWorkshop implements IBikeMaintenance { public void cleanExterior() { // … } public void lubeChain() { // … } // Other operations that are common to all bikes } //////////////////////////////////////////////////////////////////// /* * A good solution comes from rethinking our abstractions and using * a more generic the interface that relies on the parameters of the * operations to handle the differences. In such case, we would have * ended up with a simpler design that would have respected both the OCP * and the ISP principles. In some cases this is enough and can work */ public interface IMaintainable { public void void maintain(); } public abstract class MaintenanceOperation { // The only thing we want to ensure is that all operations can be executed abstract void execute(); } /* * We can now extend the MaintenanceOperation class to reflect the different * maintenance operations that each vehicle type can accept */ public enum CarMaintenanceTypes { CLEANEXTERIOR, CHANGEWHEEL, CHANGEENGINEOIL, CHECKBATTERY } } public enum BikeMaintenanceTypes { CLEANEXTERIOR, LUBECHAIN } public class CarMaintenanceOperation extends MaintenanceOperation { private CarMaintenanceTypes opType; private CarMaintenanceParameters opParams; public CarMaintenanceOperation (CarMaintenanceTypes opType, CarMaintenanceParameters opParams){ this.opType = opType; this.opParams = opParams; } public void execute(){ // specific implementation } } public class BikeMaintenanceOperation extends MaintenanceOperation { private BikeMaintenanceTypes opType; private BikeMaintenanceParameters opParams; public CarMaintenanceOperation (BikeMaintenanceTypes opType, BikeMaintenanceParameters opParams){ this.opType = opType; this.opParams = opParams; } public void execute(){ // specific implementation } } public class Vehicle implements IMaintainable { // Vehicle data and operations Collection<MaintenanceOperation> pendingOperations = new ArrayList<MaintenanceOperation>(); public void void maintain(){ Iterator itr=pendingOperations.iterator(); while(itr.hasNext()){ itr.next().execute(); } } } /* * The only thing we have to do now it to make sure each vehicle is * only assigned to maintenance operations that are applicable to it */ public class Car extends Vehicle { public void addPendingMaintenanceOperation(CarMaintenanceOperation op); // … } public class Bike extends Vehicle { public void addPendingMaintenanceOperation(BikeMaintenanceOperation op); // … } //////////////////////////////////////////////////////////////////// |