Skip to main content

Interface Segregation Principle in TypeScript

"No client should be forced to depend on interfaces they do not use."

Robert C. Martin

This principle is all about reducing the side effects and frequency of required changes by splitting large, complex interfaces into smaller, more specific ones. If an interface is broken down into specific sets of methods, it will allow the client to only know about the methods that are of interest to them.

In layman's terms, don't add additional functionality to an existing interface by adding new methods. Instead, create a new interface.

Understanding the Interface Segregation Principle

Let's say we have an interface Machine that has methods for print, scan and fax.

interface Machine {
print(document: Document): void;

scan(document: Document): void;

fax(document: Document): void;
}

class MultiFunctionPrinter implements Machine {
print(document: Document): void {
// actual implementation
}

scan(document: Document): void {
// actual implementation
}

fax(document: Document): void {
// actual implementation
}
}

Now, what if you want to introduce a simple printer that only supports printing? In the above scenario, you would be forced to implement scan and fax methods as well, even though you don't need them. This is clearly a violation of the Interface Segregation Principle.

A better approach is to segregate Machine into more specific interfaces, like so:

interface Printer {
print(document: Document): void;
}

interface Scanner {
scan(document: Document): void;
}

interface FaxMachine {
fax(document: Document): void;
}

class SimplePrinter implements Printer {
print(document: Document): void {
// actual implementation
}
}

class MultiFunctionMachine implements Printer, Scanner, FaxMachine {
print(document: Document): void {
// actual implementation
}

scan(document: Document): void {
// actual implementation
}

fax(document: Document): void {
// actual implementation
}
}

In this way, a SimplePrinter is not forced to depend on scan or fax methods it does not use. This is the crux of the Interface Segregation Principle.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

Real World Application

Let's consider a real-world example of a web service API for a digital content platform like a blog, where you can create posts, comment on posts, and share posts.

Firstly, we might start off with a single interface that describes all the possible actions a client can perform:

interface BlogService {
createPost(post: Post): void;

commentOnPost(comment: Comment): void;

sharePost(post: Post): void;
}

However, as the application grows and starts to deal with different kinds of users, we might find that this interface is not suitable for all clients. For instance, a user who only has read-only permissions should not be able to create a post. Similarly, a user who can create posts may not be able to share them.

To comply with the Interface Segregation Principle, we should instead segregate this BlogService interface into separate, more specific interfaces:

interface PostCreator {
createPost(post: Post): void;
}

interface CommentCreator {
commentOnPost(comment: Comment): void;
}

interface PostSharer {
sharePost(post: Post): void;
}

Now, we can have different classes implement these interfaces according to their actual capabilities:

class Admin implements PostCreator, CommentCreator, PostSharer {
createPost(post: Post): void {
// Actual implementation
}

commentOnPost(comment: Comment): void {
// Actual implementation
}

sharePost(post: Post): void {
// Actual implementation
}
}

class RegularUser implements CommentCreator, PostSharer {
commentOnPost(comment: Comment): void {
// Actual implementation
}

sharePost(post: Post): void {
// Actual implementation
}
}

class ReadOnlyUser {
// Doesn't implement any of the interfaces because they can't perform any of these actions.
}

Now, each client is only dependent on the interfaces they actually use. An Admin has access to all functionalities while a RegularUser can only comment on and share posts. A ReadOnlyUser does not depend on any of these interfaces as they don't have these capabilities. This is an application of the Interface Segregation Principle in a real-world scenario.

This design has the advantage of clearly delineating which roles have which capabilities and makes the code more adaptable to future changes. For instance, if we want to add a new action that only Admin users can perform, we can just create a new interface for that action and have the Admin class implement it, without affecting any of the other classes.

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for software developers.

YouTube @cloudaffle