The Interface Segregation Principle With Lambdas

The Interface Segregation Principle (ISP) is the “I” in the SOLID acronym and states that clients should not be forced to depend on methods they don’t use.

It attempts to decouple clients from unnecessary details.

Garden in blossom by Kazimir Malevich

The Problem

class UserService {

User register(String username) { … }

User find(String username) { … }

void lock(User user) { … }
}

class UserRegistrationClient {

private UserService userService;

/*constructor*/

void registerUser() {
String username = …

User user = userService
.register(username);

}
}

The UserRegistrationClient class doesn’t need anything else than the register method from the UserService, but it does depend on the whole API.

A solution according the ISP would be to create an explicit contract only for the needs of the particular client:

interface RegisterUser {

User register(String username);
}

class UserService implements RegisterUser {

/* as above */
}

class UserRegistrationClient {

private RegisterUser registerUser;

/*constructor*/

void registerUser() {
String username = …

User user = registerUser
.register(username);

}
}

This already looks much better. The benefits are pretty obvious: the client code is decoupled from unnecessary details and an explicit contract between the service provider and client is established. This is a great way for the client to express her needs and to ensure it is implemented by the provider.

However, there are drawbacks, too. Any new interface brings certain complexity and following the approach strictly ends up writing more code, in extreme cases it could lead to an explosion of interfaces. Another problem appears when we don’t have the provider’s code in the hand.

The Solution

class UserService {

/* as above */
}

class UserRegistrationClient {

private Function<String,User> registerUser;

/*constructor*/

void registerUser() {
String username = …

User user = registerUser
.apply(username);

}
}

The functionality is injected into the client using the provider:

new UserRegistrationClient(
userService::register
);

Again, this solution doesn’t come without drawbacks: we have lost the explicit and named contract and the code is less type-safety. On the other hand, we have much less code to maintain and gained a great flexibility as any function can be injected to implement the client’s needs, not just derivations of RegisterUser. And of course, third-parties are no problem anymore.

The technique can be used even in languages without interfaces, such as JavaScript:

class UserRegistrationClient {

constructor(registerUser) {
this._registerUser = registerUser;
}

registerUser() {
let username = …
this._registerUser(username);

}
}

Rather than:

class UserRegistrationClient {

constructor(userService) {
this._userService = userService;
}

registerUser() {
let username = …
this._userService
.register(username);

}
}

Conclusion

Moreover, the technique is not very common in languages where lambdas were introduced later than interfaces, such as Java or C#.

Originally published on my blog.

Software developer and occasional blogger: https://blog.ttulka.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store