9.5 KiB
id, title, category, priority, tags, related
| id | title | category | priority | tags | related | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| solid-isp-interfaces | SOLID - Interface Segregation (Small Interfaces) | solid-principles | critical |
|
|
Interface Segregation Principle - Small Cohesive Interfaces
Interfaces should be small and cohesive, grouping only closely related methods. Split large interfaces into smaller, more focused ones.
Bad Example
// Anti-pattern: Large interface with unrelated methods grouped together
interface Document {
// Content operations
getContent(): string;
setContent(content: string): void;
appendContent(content: string): void;
// Persistence operations
save(): Promise<void>;
load(id: string): Promise<void>;
delete(): Promise<void>;
// Export operations
exportToPdf(): Promise<Buffer>;
exportToWord(): Promise<Buffer>;
exportToHtml(): Promise<string>;
// Collaboration operations
share(userId: string): Promise<void>;
getCollaborators(): Promise<User[]>;
addComment(comment: Comment): Promise<void>;
getComments(): Promise<Comment[]>;
// Version control
createVersion(): Promise<Version>;
getVersionHistory(): Promise<Version[]>;
revertToVersion(versionId: string): Promise<void>;
// Permissions
setPermissions(permissions: Permissions): Promise<void>;
getPermissions(): Promise<Permissions>;
checkPermission(userId: string, action: string): Promise<boolean>;
}
// Simple note-taking app forced to implement everything
class SimpleNote implements Document {
private content: string = '';
getContent(): string { return this.content; }
setContent(content: string): void { this.content = content; }
appendContent(content: string): void { this.content += content; }
// Forced to implement methods it doesn't need
async save(): Promise<void> { throw new Error('Not supported'); }
async load(id: string): Promise<void> { throw new Error('Not supported'); }
async delete(): Promise<void> { throw new Error('Not supported'); }
async exportToPdf(): Promise<Buffer> { throw new Error('Not supported'); }
async exportToWord(): Promise<Buffer> { throw new Error('Not supported'); }
async exportToHtml(): Promise<string> { throw new Error('Not supported'); }
async share(userId: string): Promise<void> { throw new Error('Not supported'); }
async getCollaborators(): Promise<User[]> { throw new Error('Not supported'); }
async addComment(comment: Comment): Promise<void> { throw new Error('Not supported'); }
async getComments(): Promise<Comment[]> { throw new Error('Not supported'); }
async createVersion(): Promise<Version> { throw new Error('Not supported'); }
async getVersionHistory(): Promise<Version[]> { throw new Error('Not supported'); }
async revertToVersion(versionId: string): Promise<void> { throw new Error('Not supported'); }
async setPermissions(permissions: Permissions): Promise<void> { throw new Error('Not supported'); }
async getPermissions(): Promise<Permissions> { throw new Error('Not supported'); }
async checkPermission(userId: string, action: string): Promise<boolean> { throw new Error('Not supported'); }
}
Good Example
// Correct approach: Small, cohesive interfaces
// Core content interface - the essence of a document
interface DocumentContent {
getContent(): string;
setContent(content: string): void;
}
// Extended content operations
interface EditableContent extends DocumentContent {
appendContent(content: string): void;
insertContent(position: number, content: string): void;
deleteRange(start: number, end: number): void;
}
// Persistence operations
interface Persistable {
save(): Promise<void>;
load(id: string): Promise<void>;
}
interface Deletable {
delete(): Promise<void>;
}
// Export capabilities
interface PdfExportable {
exportToPdf(): Promise<Buffer>;
}
interface WordExportable {
exportToWord(): Promise<Buffer>;
}
interface HtmlExportable {
exportToHtml(): Promise<string>;
}
// Combine export interfaces when needed
interface FullyExportable extends PdfExportable, WordExportable, HtmlExportable {}
// Collaboration interfaces
interface Shareable {
share(userId: string, permission: Permission): Promise<void>;
unshare(userId: string): Promise<void>;
getCollaborators(): Promise<Collaborator[]>;
}
interface Commentable {
addComment(comment: Comment): Promise<void>;
removeComment(commentId: string): Promise<void>;
getComments(): Promise<Comment[]>;
}
// Version control
interface Versionable {
createVersion(message?: string): Promise<Version>;
getVersionHistory(): Promise<Version[]>;
revertToVersion(versionId: string): Promise<void>;
}
// Permissions
interface PermissionControlled {
setPermissions(permissions: Permissions): Promise<void>;
getPermissions(): Promise<Permissions>;
checkPermission(userId: string, action: string): Promise<boolean>;
}
// Simple note only implements what it needs
class SimpleNote implements DocumentContent {
private content: string = '';
getContent(): string {
return this.content;
}
setContent(content: string): void {
this.content = content;
}
}
// Persistent note adds storage
class PersistentNote implements EditableContent, Persistable {
private content: string = '';
private id: string | null = null;
constructor(private storage: StorageService) {}
getContent(): string { return this.content; }
setContent(content: string): void { this.content = content; }
appendContent(content: string): void { this.content += content; }
insertContent(position: number, content: string): void {
this.content = this.content.slice(0, position) + content + this.content.slice(position);
}
deleteRange(start: number, end: number): void {
this.content = this.content.slice(0, start) + this.content.slice(end);
}
async save(): Promise<void> {
this.id = await this.storage.save(this.content);
}
async load(id: string): Promise<void> {
this.content = await this.storage.load(id);
this.id = id;
}
}
// Full-featured collaborative document
class CollaborativeDocument implements
EditableContent,
Persistable,
Deletable,
FullyExportable,
Shareable,
Commentable,
Versionable,
PermissionControlled
{
constructor(
private storage: StorageService,
private exportService: ExportService,
private collaborationService: CollaborationService,
private versionService: VersionService,
private permissionService: PermissionService
) {}
// Implement all methods with actual functionality
// Each service handles its domain
getContent(): string { /* ... */ }
setContent(content: string): void { /* ... */ }
appendContent(content: string): void { /* ... */ }
insertContent(position: number, content: string): void { /* ... */ }
deleteRange(start: number, end: number): void { /* ... */ }
async save(): Promise<void> { /* delegate to storage */ }
async load(id: string): Promise<void> { /* delegate to storage */ }
async delete(): Promise<void> { /* delegate to storage */ }
async exportToPdf(): Promise<Buffer> { return this.exportService.toPdf(this); }
async exportToWord(): Promise<Buffer> { return this.exportService.toWord(this); }
async exportToHtml(): Promise<string> { return this.exportService.toHtml(this); }
async share(userId: string, permission: Permission): Promise<void> { /* ... */ }
async unshare(userId: string): Promise<void> { /* ... */ }
async getCollaborators(): Promise<Collaborator[]> { /* ... */ }
async addComment(comment: Comment): Promise<void> { /* ... */ }
async removeComment(commentId: string): Promise<void> { /* ... */ }
async getComments(): Promise<Comment[]> { /* ... */ }
async createVersion(message?: string): Promise<Version> { /* ... */ }
async getVersionHistory(): Promise<Version[]> { /* ... */ }
async revertToVersion(versionId: string): Promise<void> { /* ... */ }
async setPermissions(permissions: Permissions): Promise<void> { /* ... */ }
async getPermissions(): Promise<Permissions> { /* ... */ }
async checkPermission(userId: string, action: string): Promise<boolean> { /* ... */ }
}
// Functions can accept only the interfaces they need
function renderDocument(doc: DocumentContent): void {
console.log(doc.getContent());
}
async function exportToPdf(doc: PdfExportable): Promise<void> {
const pdf = await doc.exportToPdf();
// Send pdf...
}
function canUserEdit(doc: PermissionControlled, userId: string): Promise<boolean> {
return doc.checkPermission(userId, 'edit');
}
// All document types work where their interfaces are expected
const simpleNote = new SimpleNote();
renderDocument(simpleNote); // Works!
const collabDoc = new CollaborativeDocument(/* ... */);
renderDocument(collabDoc); // Works!
await exportToPdf(collabDoc); // Works!
await canUserEdit(collabDoc, 'user123'); // Works!
Why
-
Cohesion: Each interface groups related methods.
Commentableis about comments,Versionableabout versions. -
Flexibility: Classes implement only the interfaces that match their capabilities.
-
Composability: Complex types are built by combining simple interfaces:
EditableContent & Persistable & Shareable. -
No Empty Implementations: No need for
throw new Error('Not supported')- just don't implement the interface. -
Clear Capabilities: Looking at a class's implemented interfaces tells you exactly what it can do.
-
Easier Evolution: Add new capabilities by creating new interfaces without modifying existing ones.
-
Better Typing: Functions declare exactly what they need.
exportToPdf(doc: PdfExportable)is self-documenting.