Loading learning content...
Splitting a fat interface is not an arbitrary exercise—it's a disciplined practice of finding natural seams that already exist in your code. Just as a skilled diamond cutter examines the crystal structure before making incisions, a skilled engineer studies usage patterns, cohesion relationships, and domain boundaries before splitting interfaces.
The goal isn't simply to have more interfaces. It's to have interfaces that align with how clients actually use them, reducing unnecessary coupling and enabling independent evolution. This page teaches you the systematic techniques for identifying where those natural split boundaries lie.
By the end of this page, you will master multiple techniques for identifying interface split boundaries: client-usage analysis, cohesion measurement, domain boundary recognition, change-frequency analysis, and semantic clustering. You'll develop the ability to look at any fat interface and see the natural fault lines where it should be divided.
Before diving into techniques, let's understand what we're optimizing for when we identify split boundaries.
A good split boundary:
A poor split boundary:
Splitting interfaces at wrong boundaries doesn't improve your design—it makes it worse. Over-granular interfaces force clients to depend on multiple tiny interfaces for simple operations, increasing coupling rather than reducing it. The goal is to find the RIGHT boundaries, not just to have MORE interfaces.
Let's examine a fat interface and see how different splitting strategies yield different results:
1234567891011121314151617181920212223242526272829303132333435
// A typical fat interface that has grown over timeinterface IDocumentService { // Document CRUD operations createDocument(content: string, metadata: DocumentMeta): Document; getDocument(id: string): Document | null; updateDocument(id: string, content: string): void; deleteDocument(id: string): void; // Search operations searchDocuments(query: string): Document[]; searchByTag(tag: string): Document[]; searchByDateRange(start: Date, end: Date): Document[]; // Sharing and permissions shareDocument(docId: string, userId: string, permission: Permission): void; revokeAccess(docId: string, userId: string): void; getDocumentPermissions(docId: string): PermissionMap; // Version control getVersionHistory(docId: string): Version[]; revertToVersion(docId: string, versionId: string): void; compareVersions(versionId1: string, versionId2: string): Diff; // Export operations exportToPDF(docId: string): Buffer; exportToWord(docId: string): Buffer; exportToHTML(docId: string): string; // Analytics getViewCount(docId: string): number; getEditHistory(docId: string): EditEvent[]; getMostActiveEditors(docId: string): UserStats[];} // Where should we split this? Let's analyze systematically...The most direct way to find split boundaries is to analyze how clients actually use the interface. This technique examines which methods are called together by each client, revealing natural groupings.
The Process:
Let's apply this to our document service example:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
/** * CLIENT-USAGE ANALYSIS * * Clients of IDocumentService and the methods they use: * * ┌─────────────────────────┬───────────────────────────────────────────────────┐ * │ Client │ Methods Used │ * ├─────────────────────────┼───────────────────────────────────────────────────┤ * │ DocumentEditor │ createDocument, getDocument, updateDocument │ * │ DocumentSearchPage │ searchDocuments, searchByTag, searchByDateRange │ * │ ShareDialog │ shareDocument, revokeAccess, getDocumentPermissions│ * │ VersionHistoryPanel │ getVersionHistory, revertToVersion, compareVersions│ * │ ExportButton │ exportToPDF, exportToWord, exportToHTML │ * │ AnalyticsDashboard │ getViewCount, getEditHistory, getMostActiveEditors│ * │ DocumentListView │ getDocument, searchDocuments, deleteDocument │ * │ AdminPanel │ deleteDocument, getDocumentPermissions, getEditHistory│ * └─────────────────────────┴───────────────────────────────────────────────────┘ * * OBSERVATIONS: * * 1. CRUD methods (create, get, update, delete) appear across multiple clients * → Core interface that many clients need * * 2. Search methods cluster together (DocumentSearchPage, DocumentListView) * → Distinct searchable role * * 3. Permission methods only used by ShareDialog * → Isolated permission role * * 4. Version methods only used by VersionHistoryPanel * → Isolated versioning role * * 5. Export methods only used by ExportButton * → Isolated export role * * 6. Analytics methods split between AnalyticsDashboard and AdminPanel * → Distinct analytics role */ // Derived interface split based on CLIENT-USAGE ANALYSIS: // Core operations - used by most clientsinterface DocumentRepository { createDocument(content: string, metadata: DocumentMeta): Document; getDocument(id: string): Document | null; updateDocument(id: string, content: string): void; deleteDocument(id: string): void;} // Search operations - used by search-focused clientsinterface DocumentSearchable { searchDocuments(query: string): Document[]; searchByTag(tag: string): Document[]; searchByDateRange(start: Date, end: Date): Document[];} // Permission operations - isolated to sharing UIinterface DocumentPermissionManager { shareDocument(docId: string, userId: string, permission: Permission): void; revokeAccess(docId: string, userId: string): void; getDocumentPermissions(docId: string): PermissionMap;} // Version operations - isolated to version control UIinterface DocumentVersionControl { getVersionHistory(docId: string): Version[]; revertToVersion(docId: string, versionId: string): void; compareVersions(versionId1: string, versionId2: string): Diff;} // Export operations - isolated to export functionalityinterface DocumentExporter { exportToPDF(docId: string): Buffer; exportToWord(docId: string): Buffer; exportToHTML(docId: string): string;} // Analytics operations - used by analytics/admin clientsinterface DocumentAnalytics { getViewCount(docId: string): number; getEditHistory(docId: string): EditEvent[]; getMostActiveEditors(docId: string): UserStats[];}Key Insight: The client-usage analysis revealed that most clients use only a small subset of the 18 methods. The resulting 6 interfaces average 3 methods each—much easier to implement, mock, and understand.
Benefits of This Split:
| Client | Previously | Now |
|---|---|---|
| DocumentEditor | Depended on 18 methods, used 3 | Depends on 3 methods, uses 3 |
| ShareDialog | Depended on 18 methods, used 3 | Depends on 3 methods, uses 3 |
| ExportButton | Depended on 18 methods, used 3 | Depends on 3 methods, uses 3 |
Every client now depends only on what it uses.
For large codebases, create usage matrices programmatically. Use your IDE's 'Find Usages' feature, or write a simple script that greps each method name and identifies the calling files. Visualization tools like heatmaps make patterns immediately obvious—methods that cluster together in client code should cluster together in interfaces.
Cohesion measures how strongly related the methods within an interface are. High cohesion means methods belong together; low cohesion suggests the interface should be split.
Types of cohesion (from strongest to weakest):
The key insight: Methods with functional, sequential, or communicational cohesion should stay together. Methods with only temporal, logical, or coincidental cohesion are candidates for splitting.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
/** * COHESION ANALYSIS of IDocumentService * * Question: Do these methods share data, sequence, or function? */ // HIGH COHESION GROUP 1: Document Lifecycle// These methods share the Document entity and form a lifecycle// Cohesion type: COMMUNICATIONAL (operate on same data)interface DocumentLifecycle { createDocument(content: string, metadata: DocumentMeta): Document; getDocument(id: string): Document | null; updateDocument(id: string, content: string): void; deleteDocument(id: string): void;} // HIGH COHESION GROUP 2: Version Operations// These methods share the Version entity and have sequential dependencies// Cohesion type: SEQUENTIAL (getVersionHistory → compareVersions → revertToVersion)interface DocumentVersioning { getVersionHistory(docId: string): Version[]; compareVersions(versionId1: string, versionId2: string): Diff; revertToVersion(docId: string, versionId: string): void;} // HIGH COHESION GROUP 3: Search Capabilities// These methods share the search/query function// Cohesion type: FUNCTIONAL (all perform document discovery)interface DocumentSearch { searchDocuments(query: string): Document[]; searchByTag(tag: string): Document[]; searchByDateRange(start: Date, end: Date): Document[];} // MEDIUM COHESION GROUP: Export Operations// These methods are logically similar but share no data// Cohesion type: LOGICAL (all export, different formats)// Could be split further OR kept together based on client needsinterface DocumentExport { exportToPDF(docId: string): Buffer; exportToWord(docId: string): Buffer; exportToHTML(docId: string): string;} // LOW COHESION EXAMPLE - What NOT to do:// This interface groups methods only because they involve "documents"// Cohesion type: COINCIDENTAL (arbitrary grouping)interface BadDocumentMiscInterface { searchByTag(tag: string): Document[]; // Search concern shareDocument(docId: string, userId: string): void; // Sharing concern exportToPDF(docId: string): Buffer; // Export concern getViewCount(docId: string): number; // Analytics concern // ❌ These have NOTHING in common except "document" in the parameter}Cohesion Test Questions:
For any group of methods, ask:
| Signal | Cohesion Type | Action |
|---|---|---|
| Methods form a workflow where one's output feeds another | Sequential | Keep together |
| Methods all manipulate the same core entity | Communicational | Keep together |
| Methods combine to provide a single capability | Functional | Keep together |
| Methods called at similar times (e.g., initialization) | Temporal | Evaluate carefully |
| Methods do similar things to different data | Logical | Consider splitting |
| No clear relationship between methods | Coincidental | Definitely split |
In software metrics, LCOM measures cohesion by analyzing which instance variables each method uses. High LCOM scores indicate low cohesion. While LCOM applies to classes, the principle extends to interfaces: if method signatures suggest they work with completely different data, those methods likely belong in different interfaces.
Domain-Driven Design teaches us that well-designed software reflects the domain it models. Domain boundaries — the natural separations between different areas of business concern — are often the best places to split interfaces.
Identifying Domain Boundaries:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
/** * DOMAIN BOUNDARY ANALYSIS of IDocumentService * * Let's identify which business domains each method belongs to: */ // DOMAIN: Content Management// Stakeholder: Content team, document authors// Vocabulary: document, content, metadata, version// Regulation: None specificinterface ContentManagement { createDocument(content: string, metadata: DocumentMeta): Document; getDocument(id: string): Document | null; updateDocument(id: string, content: string): void; deleteDocument(id: string): void; getVersionHistory(docId: string): Version[]; revertToVersion(docId: string, versionId: string): void; compareVersions(versionId1: string, versionId2: string): Diff;} // DOMAIN: Access Control & Compliance// Stakeholder: Security team, compliance officers// Vocabulary: permission, share, access, revoke// Regulation: GDPR, privacy laws, audit requirementsinterface AccessControl { shareDocument(docId: string, userId: string, permission: Permission): void; revokeAccess(docId: string, userId: string): void; getDocumentPermissions(docId: string): PermissionMap;} // DOMAIN: Discovery & Search// Stakeholder: UX team, information architects// Vocabulary: search, query, tag, filter// Evolution: Changes with UI improvementsinterface Discovery { searchDocuments(query: string): Document[]; searchByTag(tag: string): Document[]; searchByDateRange(start: Date, end: Date): Document[];} // DOMAIN: Integration & Export// Stakeholder: Integration team, enterprise clients// Vocabulary: export, format, PDF, Word// Evolution: New formats added over timeinterface Integration { exportToPDF(docId: string): Buffer; exportToWord(docId: string): Buffer; exportToHTML(docId: string): string;} // DOMAIN: Analytics & Insights// Stakeholder: Product team, data analysts// Vocabulary: count, stats, history, editors// Regulation: Privacy (may require anonymization)interface Analytics { getViewCount(docId: string): number; getEditHistory(docId: string): EditEvent[]; getMostActiveEditors(docId: string): UserStats[];} /** * VALIDATION: Microservices Thought Experiment * * If we split into microservices, would each interface map to a service? * * ContentService ← ContentManagement ✓ Makes sense * AccessService ← AccessControl ✓ Often a separate service * SearchService ← Discovery ✓ ElasticSearch-backed service * ExportService ← Integration ✓ Separate concerns * AnalyticsService ← Analytics ✓ Often async/CQRS * * Each interface could be a service boundary! This suggests * our domain-based split aligns with natural architectural boundaries. */The Bounded Context Alignment Test:
A powerful validation for domain-based splits is to ask: "Could each resulting interface belong to a different bounded context?"
If yes, the split respects domain boundaries. If no—if methods from the same context are scattered—reconsider the split.
Event Storming workshops can reveal domain boundaries. Map the events in your system (DocumentCreated, PermissionGranted, DocumentExported, AnalyticsRecorded) and notice which events cluster together. Commands that trigger the same events often belong in the same interface; commands triggering events in different clusters suggest split points.
Methods that change together should stay together. This principle, sometimes called the Common Closure Principle, suggests that we can find split boundaries by analyzing version control history.
The Process:
Methods that never change together have no reason to be in the same interface. When one changes, the other's clients shouldn't need to care.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
/** * CHANGE FREQUENCY ANALYSIS * * Analyzing git history for IDocumentService over 6 months: * * COMMIT HISTORY (simplified): * * Commit #125: "Add full-text search to search function" * Changed: searchDocuments, searchByTag ← Change together * * Commit #130: "Add DOCX export format" * Changed: exportToWord ← Isolated change * * Commit #135: "Fix permission caching bug" * Changed: shareDocument, getDocumentPermissions ← Change together * * Commit #142: "Update search to use Elasticsearch" * Changed: searchDocuments, searchByTag, searchByDateRange ← All search together * * Commit #150: "Add version comparison UI" * Changed: compareVersions, getVersionHistory ← Change together * * Commit #158: "Analytics GDPR compliance" * Changed: getViewCount, getEditHistory, getMostActiveEditors ← All analytics together * * Commit #165: "Permission audit logging" * Changed: shareDocument, revokeAccess ← Change together * * CHANGE-COUPLING MATRIX (0 = never change together, 5 = always change together): * * search searchTag searchDate share revoke getPerm export... * searchDocuments - 5 5 0 0 0 0 * searchByTag 5 - 5 0 0 0 0 * searchByDateRange 5 5 - 0 0 0 0 * shareDocument 0 0 0 - 3 4 0 * revokeAccess 0 0 0 3 - 2 0 * getPermissions 0 0 0 4 2 - 0 * exportToPDF 0 0 0 0 0 0 - * exportToWord 0 0 0 0 0 0 2 * ... * * CLUSTERS IDENTIFIED: */ // CLUSTER 1: High change-coupling (search methods)interface DocumentSearch { searchDocuments(query: string): Document[]; searchByTag(tag: string): Document[]; searchByDateRange(start: Date, end: Date): Document[]; // These ALWAYS change together (new search features, engine swaps)} // CLUSTER 2: High change-coupling (permission methods)interface DocumentPermissions { shareDocument(docId: string, userId: string, permission: Permission): void; revokeAccess(docId: string, userId: string): void; getDocumentPermissions(docId: string): PermissionMap; // These change together (security audits, compliance updates)} // CLUSTER 3: High change-coupling (analytics methods)interface DocumentAnalytics { getViewCount(docId: string): number; getEditHistory(docId: string): EditEvent[]; getMostActiveEditors(docId: string): UserStats[]; // These change together (privacy changes, new metrics)} // CLUSTER 4: Moderate coupling (version methods)interface DocumentVersioning { getVersionHistory(docId: string): Version[]; revertToVersion(docId: string, versionId: string): void; compareVersions(versionId1: string, versionId2: string): Diff; // These change together when version model evolves} // ISOLATED METHODS: Low change-coupling with everything// Could be individual interfaces or grouped pragmaticallyinterface DocumentExporter { exportToPDF(docId: string): Buffer; exportToWord(docId: string): Buffer; exportToHTML(docId: string): string; // Changes independently, but grouped by logical similarity}Tools like 'git log --follow -p' can show which methods changed together. More sophisticated analysis uses tools like Code Maat, CodeScene, or custom scripts that parse git diffs and detect coupling. Even a quick manual scan of recent commits often reveals obvious patterns.
The Change Frequency Principle in Practice:
When methods with high change-coupling are in the same interface:
When methods with zero change-coupling are in the same interface:
Sometimes the best split boundaries are revealed by semantic analysis of method names and signatures. Methods can be clustered by:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
/** * SEMANTIC CLUSTERING ANALYSIS * * Analyzing IDocumentService method semantics: */ // VERB CLUSTER: "CRUD" operations (create, get, update, delete)// These verbs indicate basic entity lifecycle managementinterface DocumentCRUD { createDocument(...): Document; // create- getDocument(...): Document; // get- (read) updateDocument(...): void; // update- deleteDocument(...): void; // delete-} // VERB CLUSTER: "Query" operations (search, find, get...by)// These verbs indicate discovery/retrievalinterface DocumentQueries { searchDocuments(...): Document[]; // search- searchByTag(...): Document[]; // search-by searchByDateRange(...): Document[]; // search-by} // VERB CLUSTER: "Access Control" operations (share, revoke, get...permissions)// These verbs indicate authorizationinterface DocumentAccessControl { shareDocument(...): void; // share- revokeAccess(...): void; // revoke- getDocumentPermissions(...): PermissionMap; // get-...-permissions} // VERB CLUSTER: "Temporal" operations (getHistory, revert, compare)// These verbs indicate version/time awarenessinterface DocumentHistory { getVersionHistory(...): Version[]; // get..History revertToVersion(...): void; // revert- compareVersions(...): Diff; // compare-} // VERB CLUSTER: "Transform" operations (export, convert)// These verbs indicate format conversioninterface DocumentTransform { exportToPDF(...): Buffer; // exportTo- exportToWord(...): Buffer; // exportTo- exportToHTML(...): string; // exportTo-} // NOUN CLUSTER ANALYSIS:// // Methods mentioning "Version":// - getVersionHistory, revertToVersion, compareVersions// → Natural grouping around Version entity// // Methods mentioning "Permission":// - getDocumentPermissions, shareDocument (implied), revokeAccess (implied)// → Natural grouping around Permission entity// // RETURN TYPE ANALYSIS:// // Methods returning Document or Document[]:// - createDocument, getDocument, searchDocuments, searchByTag, searchByDateRange// → These are "document providers" - core to most clients// // Methods returning specialized types:// - Version[], Diff, Permission, stats// → These serve specific, narrower use cases // ===========================================// FINAL SEMANTIC SPLIT:// =========================================== // Core document operations - most commonly neededinterface DocumentRepository { createDocument(content: string, metadata: DocumentMeta): Document; getDocument(id: string): Document | null; updateDocument(id: string, content: string): void; deleteDocument(id: string): void;} // Query/discovery operations - returns document collectionsinterface DocumentFinder { searchDocuments(query: string): Document[]; searchByTag(tag: string): Document[]; searchByDateRange(start: Date, end: Date): Document[];} // Permission operations - access control vocabularyinterface DocumentAccessController { shareDocument(docId: string, userId: string, permission: Permission): void; revokeAccess(docId: string, userId: string): void; getDocumentPermissions(docId: string): PermissionMap;} // History/version operations - temporal vocabularyinterface DocumentVersioner { getVersionHistory(docId: string): Version[]; revertToVersion(docId: string, versionId: string): void; compareVersions(versionId1: string, versionId2: string): Diff;} // Export operations - transformation vocabularyinterface DocumentExporter { exportToPDF(docId: string): Buffer; exportToWord(docId: string): Buffer; exportToHTML(docId: string): string;} // Analytics operations - metrics vocabularyinterface DocumentMetrics { getViewCount(docId: string): number; getEditHistory(docId: string): EditEvent[]; getMostActiveEditors(docId: string): UserStats[];}After splitting, can you name each interface with a single, clear noun or agent noun (Repository, Finder, Exporter, Versioner)? If an interface is hard to name, it probably combines multiple concerns. If the best name is something generic like 'MiscOperations' or 'HelperMethods', the split boundary is wrong.
In practice, the best split boundaries are identified by combining multiple techniques. Each technique has strengths and limitations:
| Technique | Best For | Limitations |
|---|---|---|
| Client-Usage Analysis | Finding real client needs | Requires existing clients |
| Cohesion Analysis | Ensuring methods belong together | Subjective interpretation |
| Domain Boundaries | Matching business structure | May over-split |
| Change Frequency | Minimizing change impact | Requires history |
| Semantic Clustering | Quick initial analysis | Surface-level only |
The Convergence Principle: When multiple techniques suggest the same split boundary, you've found a strong candidate. When techniques disagree, investigate further—one technique may reveal something others miss.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
/** * SPLIT DECISION MATRIX FOR IDocumentService * * Score: 0 = suggests don't split here, 1 = suggests split here * * ┌────────────────────────┬───────────┬───────────┬───────────┬───────────┬───────────┐ * │ Split Boundary │ Client │ Cohesion │ Domain │ Change │ Semantic │ * │ │ Usage │ Analysis │ Boundary │ Frequency │ Cluster │ * ├────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ * │ CRUD vs Search │ 1 │ 1 │ 0 │ 1 │ 1 │ * │ SCORE: 4/5 ✓ STRONG │ │ │ │ │ │ * ├────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ * │ Search vs Permissions │ 1 │ 1 │ 1 │ 1 │ 1 │ * │ SCORE: 5/5 ✓ DEFINITE │ │ │ │ │ │ * ├────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ * │ Permissions vs Version │ 1 │ 1 │ 1 │ 1 │ 1 │ * │ SCORE: 5/5 ✓ DEFINITE │ │ │ │ │ │ * ├────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ * │ Version vs Export │ 1 │ 1 │ 1 │ 1 │ 1 │ * │ SCORE: 5/5 ✓ DEFINITE │ │ │ │ │ │ * ├────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ * │ Export vs Analytics │ 1 │ 1 │ 1 │ 1 │ 1 │ * │ SCORE: 5/5 ✓ DEFINITE │ │ │ │ │ │ * ├────────────────────────┼───────────┼───────────┼───────────┼───────────┼───────────┤ * │ CRUD ↔ Version │ 0 │ 0 │ 0 │ 0 │ 0 │ * │ SCORE: 0/5 ✗ KEEP │ (Both used│ (Both work│ (Same │ (Change │ (Different│ * │ TOGETHER │ by editor)│ on docs) │ context) │ together) │ verbs!) │ * └────────────────────────┴───────────┴───────────┴───────────┴───────────┴───────────┘ * * WAIT! The last row shows CRUD and Version might belong together! * Let's investigate: DocumentEditor uses both create/update AND version history. * In Domain terms, versions ARE part of content management. * * REVISED SPLIT considering this insight: */ // Combined: Core document + version operations (content management domain)interface DocumentContentManager { // CRUD createDocument(content: string, metadata: DocumentMeta): Document; getDocument(id: string): Document | null; updateDocument(id: string, content: string): void; deleteDocument(id: string): void; // Versioning - strongly coupled to content getVersionHistory(docId: string): Version[]; revertToVersion(docId: string, versionId: string): void; compareVersions(versionId1: string, versionId2: string): Diff;} // Separate: Discovery operationsinterface DocumentDiscovery { searchDocuments(query: string): Document[]; searchByTag(tag: string): Document[]; searchByDateRange(start: Date, end: Date): Document[];} // Separate: Access controlinterface DocumentAccessControl { shareDocument(docId: string, userId: string, permission: Permission): void; revokeAccess(docId: string, userId: string): void; getDocumentPermissions(docId: string): PermissionMap;} // Separate: Export/Integrationinterface DocumentExporter { exportToPDF(docId: string): Buffer; exportToWord(docId: string): Buffer; exportToHTML(docId: string): string;} // Separate: Analyticsinterface DocumentAnalytics { getViewCount(docId: string): number; getEditHistory(docId: string): EditEvent[]; getMostActiveEditors(docId: string): UserStats[];} /** * FINAL RESULT: 5 interfaces instead of 6 * * We combined CRUD + Versioning because ALL techniques agreed * they're more related to each other than to other groups. * * This is the power of using multiple techniques! */The decision matrix revealed that CRUD and Versioning, which seemed like separate concerns, are actually tightly coupled. Multiple techniques agreeing on a split = high confidence. Multiple techniques disagreeing = investigate further before splitting.
Not all splits improve design. Some splitting patterns actively harm code quality. Learn to recognize and avoid these anti-patterns:
IDocumentCreator, IDocumentGetter, IDocumentUpdater. This fragments cohesive CRUD operations, forcing clients to depend on 4 interfaces for basic entity management. Over-granular splits increase coupling, not decrease it.getX() from setX() can force clients to depend on both interfaces anyway.IDocumentServiceA_M and IDocumentServiceN_Z provide zero semantic value.DatabaseMethods, CacheMethods, ApiMethods). This leaks implementation details into interface design.createX, updateX, deleteX, you must have separate Creator, Updater, Deleter interfaces. Symmetry is not a design goal.12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
// ❌ ANTI-PATTERN: One-Method-Per-Interface// Over-granular - forces clients to depend on 4 tiny interfacesinterface IDocumentCreator { createDocument(content: string): Document;}interface IDocumentReader { getDocument(id: string): Document;}interface IDocumentUpdater { updateDocument(id: string, content: string): void;}interface IDocumentDeleter { deleteDocument(id: string): void;} // Client code is now a nightmareclass DocumentEditor { constructor( private creator: IDocumentCreator, private reader: IDocumentReader, private updater: IDocumentUpdater, private deleter: IDocumentDeleter // 4 dependencies for basic CRUD! ) {}} // ✓ CORRECT: Cohesive CRUD interfaceinterface DocumentRepository { createDocument(content: string): Document; getDocument(id: string): Document; updateDocument(id: string, content: string): void; deleteDocument(id: string): void;} class BetterDocumentEditor { constructor(private documents: DocumentRepository) {} // Single, cohesive dependency} // ❌ ANTI-PATTERN: Technology-Based Split// Leaks implementation into interface designinterface DatabaseDocumentOps { saveToDatabase(doc: Document): void; loadFromDatabase(id: string): Document;}interface CacheDocumentOps { cacheDocument(doc: Document): void; getFromCache(id: string): Document | null;} // ✓ CORRECT: Behavior-Based Interfaceinterface DocumentPersistence { save(doc: Document): void; load(id: string): Document; // Implementation chooses database, cache, or both}Interfaces that are too large force unnecessary dependencies. Interfaces that are too small force artificial fragmentation. The goal is interfaces that are 'just right' — cohesive groupings that match client needs. When in doubt, err on the side of larger, more cohesive interfaces. You can always split later; merging is harder.
We've covered five systematic techniques for identifying interface split boundaries. Let's consolidate this into an actionable toolkit:
Next Steps:
With split boundaries identified, the next challenge is implementing multiple interfaces in a single class while maintaining clean architecture. The following page covers practical patterns for multiple interface implementation, including composition strategies, role composition, and implementation organization techniques.
You now possess a systematic toolkit for identifying interface split boundaries. Remember: the goal isn't to have more interfaces—it's to have interfaces that align with how clients actually use them, reducing unnecessary coupling and enabling independent evolution. Apply these techniques thoughtfully, and your interface designs will be both flexible and maintainable.