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.
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 DETAILSReal 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.