No code should be forced to depend on methods it does not use.
Imagine a class implementing/extending another class and forced to implement all methods from the base class even if all the methods are not exactly relevant. How does it sound? According to Interface Segregation Principle (ISP), it does not sound good.
Besides not having a clean code base, violating ISP causes some other side effects such as fragile and tight coupled code base and reduced maintainability.
These side effects are particularly significant because they impact flexibility of a system.
Let’s go go ahead with a simple example violating ISP. Imagine that you are working on a transportation application and your requirements indicate that you should provide authentication and update profile settings feature for both passenger and staff users. However in your requirements there is a detail about the staff users.
- Sfaff users are registered through an admin panel.
As we understand from the line above, our app should not provide register functionality for staff users.
With the given example use case, let’s have a look at the Dart example below:
abstract interface class IUserProfile {
void signIn();
void signOut();
void register();
void updateSetting();
}
class PassengerProfile implements IUserProfile {
@override
void register() {
print('Registered as passenger');
}
@override
void signIn() {
print('Signed in as passenger');
}
@override
void signOut() {
print('Signed out as passenger');
}
@override
void updateSetting() {
print('Updated profiel settings');
}
}
class StaffProfile implements IUserProfile {
@override
void register() {
// Registraion is not supported for staff users in the app.
throw UnimplementedError();
}
@override
void signIn() {
print('Signed in as staff');
}
@override
void signOut() {
print('Signed out as staff');
}
@override
void updateSetting() {
print('Updated profile settings');
}
}
Everything looks good so far. We have IUserProfile interface, PassengerProfile and StaffProfile implementing the IUserProfile.
Hmm. What about our requirements? Why do we have a register function in StaffProfile class even if it does not have to be there? Assume another developer is responsible for fixing a bug in this module and seeing the register function inside the StaffProfile. What would he/she think? Is not it just confusing? It is confusing!
Apart from that this code snippet can create many questions in mind without answers, violating ISP can cause bigger problems.
Let’s try to achieve a better structure and adhere ISP.
abstract interface class IUserProfile {
void signIn();
void signOut();
void updateSetting();
}
abstract interface class IPassengerProfile implements IUserProfile {
void register();
}
abstract interface class IStaffProfile implements IUserProfile {}
class PassengerProfile implements IPassangerProfile {
@override
void register() {
print('Registered as passenger');
}
@override
void signIn() {
print('Signed in as passenger');
}
@override
void signOut() {
print('Signed out as passenger');
}
@override
void updateSetting() {
print('Updated profile settings');
}
}
class StaffProfile implements IStaffProfile {
@override
void signIn() {
print('Signed in as staff');
}
@override
void signOut() {
print('Signed out as staff');
}
@override
void updateSetting() {
print('Updated profiel settings');
}
}
Although many different approaches can be utilized, I’ve chosen a really simple one.
ISP stands for segregating fat interfaces into smaller meaningful interfaces. We already had IUserProfile, and with the new example just created two new interfaces IPassengerProfile and IStaffProfile.
As you can see PassengerProfile and StaffProfile are not forced to implement any irrelevant functions.
Reference:
https://en.wikipedia.org/wiki/Interface_segregation_principle