Prerequisites
- Understanding of interfaces 📝
- Knowledge of inheritance 🔍
- Basic SOLID principles awareness 💻
What you'll learn
- Understand Interface Segregation Principle 🎯
- Identify and fix interface pollution 🏗️
- Design cohesive, focused interfaces 🛡️
- Apply ISP in real-world scenarios ✨
🎯 Introduction
Welcome to the world of clean interface design! 🎉 In this guide, we’ll explore the Interface Segregation Principle (ISP), one of the five SOLID principles that helps create maintainable, flexible software architectures.
You’ll discover how ISP is like a well-organized toolbox 🧰 - instead of one massive Swiss Army knife that does everything, you have specific tools for specific jobs. Whether you’re designing APIs 🌐, building component libraries 📚, or creating service layers 🏗️, understanding ISP is crucial for writing professional-grade TypeScript code.
By the end of this tutorial, you’ll be confidently designing interfaces that are focused, cohesive, and a joy to work with! Let’s segregate! 🏊♂️
📚 Understanding Interface Segregation Principle
🤔 What is ISP?
The Interface Segregation Principle states: “Clients should not be forced to depend on interfaces they don’t use.” In simpler terms, it’s better to have many small, focused interfaces than one large, all-encompassing interface.
Think of it like a restaurant menu 🍽️:
- ❌ Bad: One giant menu with breakfast, lunch, dinner, desserts, and drinks all mixed together
- ✅ Good: Separate menus for breakfast, lunch, dinner, with focused options
💡 Why ISP Matters
Here’s why developers embrace ISP:
- Flexibility 🤸♂️: Classes implement only what they need
- Maintainability 🔧: Changes to one interface don’t affect unrelated code
- Testability 🧪: Easier to mock and test focused interfaces
- Clarity 🎯: Clear contracts and responsibilities
Real-world example: Imagine a printer interface 🖨️. Not all printers can scan, fax, or staple. Following ISP, we’d have separate interfaces for Printable
, Scannable
, Faxable
, and Stapleable
, allowing each printer to implement only what it actually supports.
🔧 Identifying Interface Pollution
📝 Signs of a Polluted Interface
Let’s start by recognizing when interfaces violate ISP:
// ❌ BAD: Fat interface with too many responsibilities
interface Employee {
// Personal info
id: string;
name: string;
email: string;
phone: string;
address: string;
// Work info
department: string;
position: string;
salary: number;
startDate: Date;
// Methods for ALL employees
work(): void;
attendMeeting(): void;
submitTimesheet(): void;
// Manager-specific methods
approveTimesheet(employeeId: string): void;
conductPerformanceReview(employeeId: string): void;
assignTask(employeeId: string, task: string): void;
// Developer-specific methods
writeCode(): void;
reviewCode(pullRequestId: string): void;
deployApplication(): void;
// HR-specific methods
processPayroll(): void;
handleBenefits(): void;
recruitNewEmployee(): void;
// Accounting-specific methods
generateFinancialReport(): void;
processExpenseReport(reportId: string): void;
auditDepartmentBudget(): void;
}
// 😱 Now every employee type must implement ALL methods!
class Developer implements Employee {
// ... all personal and work properties ...
work() { console.log('Writing code...'); }
attendMeeting() { console.log('Attending standup...'); }
submitTimesheet() { console.log('Submitting hours...'); }
// ❌ Forced to implement manager methods
approveTimesheet(employeeId: string) {
throw new Error('Developers cannot approve timesheets!');
}
conductPerformanceReview(employeeId: string) {
throw new Error('Developers cannot conduct reviews!');
}
assignTask(employeeId: string, task: string) {
throw new Error('Developers cannot assign tasks!');
}
// ✅ Developer methods make sense
writeCode() { console.log('Writing TypeScript...'); }
reviewCode(pullRequestId: string) { console.log('Reviewing PR...'); }
deployApplication() { console.log('Deploying to production...'); }
// ❌ Forced to implement HR methods
processPayroll() {
throw new Error('Developers cannot process payroll!');
}
handleBenefits() {
throw new Error('Developers cannot handle benefits!');
}
recruitNewEmployee() {
throw new Error('Developers cannot recruit!');
}
// ❌ Forced to implement accounting methods
generateFinancialReport() {
throw new Error('Developers cannot generate financial reports!');
}
processExpenseReport(reportId: string) {
throw new Error('Developers cannot process expenses!');
}
auditDepartmentBudget() {
throw new Error('Developers cannot audit budgets!');
}
}
// 🤯 This is a nightmare to maintain and use!
🚨 Problems with Fat Interfaces
- Unused Methods 🗑️: Classes implement methods they’ll never use
- Coupling 🔗: Changes affect unrelated classes
- Confusion 😵: Unclear what each class actually does
- Testing Nightmare 🧪: Must mock/stub unnecessary methods
🏗️ Applying Interface Segregation
✨ The Solution: Segregated Interfaces
Let’s refactor using ISP:
// ✅ GOOD: Segregated interfaces following ISP
// 🎯 Core employee information
interface Person {
id: string;
name: string;
email: string;
phone: string;
address: string;
}
// 💼 Basic employee behavior
interface Employee extends Person {
department: string;
position: string;
salary: number;
startDate: Date;
work(): void;
attendMeeting(): void;
submitTimesheet(): void;
}
// 👔 Manager capabilities
interface Manager {
approveTimesheet(employeeId: string): void;
conductPerformanceReview(employeeId: string): void;
assignTask(employeeId: string, task: string): void;
manageBudget(amount: number): void;
}
// 💻 Developer capabilities
interface Developer {
writeCode(language: string): void;
reviewCode(pullRequestId: string): void;
debugApplication(issueId: string): void;
}
// 🚀 Deployment capabilities
interface Deployer {
deployApplication(version: string): void;
rollbackDeployment(version: string): void;
monitorDeployment(): void;
}
// 👥 HR capabilities
interface HRSpecialist {
processPayroll(): void;
handleBenefits(employeeId: string): void;
recruitNewEmployee(position: string): void;
conductOnboarding(employeeId: string): void;
}
// 💰 Accounting capabilities
interface Accountant {
generateFinancialReport(period: string): void;
processExpenseReport(reportId: string): void;
auditDepartmentBudget(department: string): void;
forecastBudget(quarters: number): void;
}
// 🎨 Now we can compose employee types with only what they need!
// 💻 Software Developer - implements only relevant interfaces
class SoftwareDeveloper implements Employee, Developer {
// Employee properties
id: string;
name: string;
email: string;
phone: string;
address: string;
department: string;
position: string;
salary: number;
startDate: Date;
constructor(personalInfo: Person, workInfo: Partial<Employee>) {
Object.assign(this, personalInfo, workInfo);
}
// Employee methods
work(): void {
console.log(`${this.name} is coding...`);
}
attendMeeting(): void {
console.log(`${this.name} is in a standup meeting...`);
}
submitTimesheet(): void {
console.log(`${this.name} submitted timesheet`);
}
// Developer methods
writeCode(language: string): void {
console.log(`${this.name} is writing ${language} code`);
}
reviewCode(pullRequestId: string): void {
console.log(`${this.name} is reviewing PR #${pullRequestId}`);
}
debugApplication(issueId: string): void {
console.log(`${this.name} is debugging issue #${issueId}`);
}
}
// 🚀 Senior Developer with deployment permissions
class SeniorDeveloper extends SoftwareDeveloper implements Deployer {
deployApplication(version: string): void {
console.log(`${this.name} is deploying version ${version}`);
}
rollbackDeployment(version: string): void {
console.log(`${this.name} is rolling back to version ${version}`);
}
monitorDeployment(): void {
console.log(`${this.name} is monitoring deployment metrics`);
}
}
// 👔 Engineering Manager
class EngineeringManager implements Employee, Manager, Developer {
// Properties
id: string;
name: string;
email: string;
phone: string;
address: string;
department: string;
position: string;
salary: number;
startDate: Date;
teamSize: number;
constructor(personalInfo: Person, workInfo: Partial<Employee>, teamSize: number) {
Object.assign(this, personalInfo, workInfo);
this.teamSize = teamSize;
}
// Employee methods
work(): void {
console.log(`${this.name} is managing the team...`);
}
attendMeeting(): void {
console.log(`${this.name} is in a leadership meeting...`);
}
submitTimesheet(): void {
console.log(`${this.name} submitted managerial timesheet`);
}
// Manager methods
approveTimesheet(employeeId: string): void {
console.log(`${this.name} approved timesheet for employee ${employeeId}`);
}
conductPerformanceReview(employeeId: string): void {
console.log(`${this.name} is reviewing employee ${employeeId}`);
}
assignTask(employeeId: string, task: string): void {
console.log(`${this.name} assigned "${task}" to employee ${employeeId}`);
}
manageBudget(amount: number): void {
console.log(`${this.name} is managing budget of $${amount}`);
}
// Developer methods (managers can still code!)
writeCode(language: string): void {
console.log(`${this.name} is helping with ${language} code`);
}
reviewCode(pullRequestId: string): void {
console.log(`${this.name} is reviewing PR #${pullRequestId} for architecture`);
}
debugApplication(issueId: string): void {
console.log(`${this.name} is helping debug critical issue #${issueId}`);
}
}
// 👥 HR Manager
class HRManager implements Employee, Manager, HRSpecialist {
// Properties
id: string;
name: string;
email: string;
phone: string;
address: string;
department: string = 'Human Resources';
position: string;
salary: number;
startDate: Date;
constructor(personalInfo: Person, workInfo: Partial<Employee>) {
Object.assign(this, personalInfo, workInfo);
}
// Employee methods
work(): void {
console.log(`${this.name} is working on HR tasks...`);
}
attendMeeting(): void {
console.log(`${this.name} is in an HR meeting...`);
}
submitTimesheet(): void {
console.log(`${this.name} submitted HR timesheet`);
}
// Manager methods
approveTimesheet(employeeId: string): void {
console.log(`${this.name} approved timesheet for ${employeeId}`);
}
conductPerformanceReview(employeeId: string): void {
console.log(`${this.name} is conducting HR review for ${employeeId}`);
}
assignTask(employeeId: string, task: string): void {
console.log(`${this.name} assigned HR task "${task}" to ${employeeId}`);
}
manageBudget(amount: number): void {
console.log(`${this.name} is managing HR budget of $${amount}`);
}
// HR methods
processPayroll(): void {
console.log(`${this.name} is processing company payroll`);
}
handleBenefits(employeeId: string): void {
console.log(`${this.name} is updating benefits for ${employeeId}`);
}
recruitNewEmployee(position: string): void {
console.log(`${this.name} is recruiting for ${position} position`);
}
conductOnboarding(employeeId: string): void {
console.log(`${this.name} is onboarding new employee ${employeeId}`);
}
}
🎨 Real-World Examples
🖨️ Printer System
Let’s design a printer system following ISP:
// 🖨️ Segregated printer interfaces
interface Printer {
print(document: Document): void;
getPrinterStatus(): PrinterStatus;
getInkLevel(): number;
}
interface Scanner {
scan(settings: ScanSettings): ScannedDocument;
getScannerStatus(): ScannerStatus;
}
interface Fax {
sendFax(number: string, document: Document): void;
receiveFax(): Document | null;
getFaxHistory(): FaxRecord[];
}
interface Copier {
copy(document: Document, copies: number): void;
getCopierStatus(): CopierStatus;
}
interface Stapler {
staple(pages: number): void;
getStaplerStatus(): StaplerStatus;
refillStaples(): void;
}
interface NetworkDevice {
connect(network: string, password: string): void;
disconnect(): void;
getNetworkStatus(): NetworkStatus;
getIPAddress(): string;
}
interface CloudEnabled {
uploadToCloud(document: Document, service: string): void;
downloadFromCloud(documentId: string, service: string): Document;
linkCloudAccount(service: string, credentials: any): void;
}
// 📄 Supporting types
interface Document {
id: string;
name: string;
content: string;
pages: number;
}
interface ScannedDocument extends Document {
resolution: number;
colorMode: 'color' | 'grayscale' | 'blackwhite';
}
interface ScanSettings {
resolution: number;
colorMode: 'color' | 'grayscale' | 'blackwhite';
format: 'pdf' | 'jpeg' | 'png';
}
interface FaxRecord {
id: string;
number: string;
timestamp: Date;
pages: number;
status: 'sent' | 'received' | 'failed';
}
type PrinterStatus = 'ready' | 'printing' | 'paper_jam' | 'out_of_paper' | 'offline';
type ScannerStatus = 'ready' | 'scanning' | 'warming_up' | 'error';
type CopierStatus = 'ready' | 'copying' | 'paper_jam' | 'maintenance';
type StaplerStatus = 'ready' | 'low_staples' | 'empty' | 'jammed';
type NetworkStatus = 'connected' | 'connecting' | 'disconnected' | 'error';
// 🖨️ Basic printer implementation
class BasicPrinter implements Printer {
private inkLevel: number = 100;
private status: PrinterStatus = 'ready';
print(document: Document): void {
console.log(`🖨️ Printing "${document.name}" (${document.pages} pages)`);
this.inkLevel -= document.pages * 0.5;
this.status = 'printing';
setTimeout(() => {
this.status = 'ready';
console.log('✅ Printing complete!');
}, 2000);
}
getPrinterStatus(): PrinterStatus {
return this.status;
}
getInkLevel(): number {
return Math.max(0, this.inkLevel);
}
}
// 🖨️ Multifunction printer
class MultifunctionPrinter implements Printer, Scanner, Copier, NetworkDevice {
private inkLevel: number = 100;
private printerStatus: PrinterStatus = 'ready';
private scannerStatus: ScannerStatus = 'ready';
private copierStatus: CopierStatus = 'ready';
private networkStatus: NetworkStatus = 'disconnected';
private ipAddress: string = '';
// Printer implementation
print(document: Document): void {
console.log(`🖨️ MFP: Printing "${document.name}" (${document.pages} pages)`);
this.inkLevel -= document.pages * 0.5;
this.printerStatus = 'printing';
setTimeout(() => {
this.printerStatus = 'ready';
console.log('✅ Printing complete!');
}, 2000);
}
getPrinterStatus(): PrinterStatus {
return this.printerStatus;
}
getInkLevel(): number {
return Math.max(0, this.inkLevel);
}
// Scanner implementation
scan(settings: ScanSettings): ScannedDocument {
console.log(`📄 MFP: Scanning at ${settings.resolution}dpi in ${settings.colorMode}`);
this.scannerStatus = 'scanning';
const scannedDoc: ScannedDocument = {
id: `scan_${Date.now()}`,
name: `Scan_${new Date().toISOString()}`,
content: 'Scanned content...',
pages: 1,
resolution: settings.resolution,
colorMode: settings.colorMode
};
setTimeout(() => {
this.scannerStatus = 'ready';
console.log('✅ Scanning complete!');
}, 1500);
return scannedDoc;
}
getScannerStatus(): ScannerStatus {
return this.scannerStatus;
}
// Copier implementation
copy(document: Document, copies: number): void {
console.log(`📋 MFP: Making ${copies} copies of "${document.name}"`);
this.copierStatus = 'copying';
this.inkLevel -= document.pages * copies * 0.5;
setTimeout(() => {
this.copierStatus = 'ready';
console.log(`✅ Created ${copies} copies!`);
}, 1000 * copies);
}
getCopierStatus(): CopierStatus {
return this.copierStatus;
}
// Network implementation
connect(network: string, password: string): void {
console.log(`🌐 MFP: Connecting to ${network}...`);
this.networkStatus = 'connecting';
setTimeout(() => {
this.networkStatus = 'connected';
this.ipAddress = `192.168.1.${Math.floor(Math.random() * 100) + 100}`;
console.log(`✅ Connected! IP: ${this.ipAddress}`);
}, 2000);
}
disconnect(): void {
console.log('🌐 MFP: Disconnecting from network...');
this.networkStatus = 'disconnected';
this.ipAddress = '';
}
getNetworkStatus(): NetworkStatus {
return this.networkStatus;
}
getIPAddress(): string {
return this.ipAddress;
}
}
// 🖨️ Enterprise printer with all features
class EnterprisePrinter extends MultifunctionPrinter implements Fax, Stapler, CloudEnabled {
private faxHistory: FaxRecord[] = [];
private staplerStatus: StaplerStatus = 'ready';
private stapleCount: number = 5000;
private cloudAccounts: Map<string, any> = new Map();
// Fax implementation
sendFax(number: string, document: Document): void {
console.log(`📠 Sending fax to ${number}: "${document.name}"`);
const record: FaxRecord = {
id: `fax_${Date.now()}`,
number,
timestamp: new Date(),
pages: document.pages,
status: 'sent'
};
this.faxHistory.push(record);
console.log('✅ Fax sent successfully!');
}
receiveFax(): Document | null {
if (Math.random() > 0.7) {
const fax: Document = {
id: `fax_${Date.now()}`,
name: `Received_Fax_${new Date().toISOString()}`,
content: 'Fax content...',
pages: Math.floor(Math.random() * 5) + 1
};
console.log(`📠 Received fax: "${fax.name}" (${fax.pages} pages)`);
this.faxHistory.push({
id: fax.id,
number: 'Unknown',
timestamp: new Date(),
pages: fax.pages,
status: 'received'
});
return fax;
}
return null;
}
getFaxHistory(): FaxRecord[] {
return [...this.faxHistory];
}
// Stapler implementation
staple(pages: number): void {
if (this.stapleCount < pages) {
this.staplerStatus = 'empty';
throw new Error('Not enough staples!');
}
console.log(`📎 Stapling ${pages} pages...`);
this.stapleCount -= pages;
if (this.stapleCount < 100) {
this.staplerStatus = 'low_staples';
}
console.log('✅ Pages stapled!');
}
getStaplerStatus(): StaplerStatus {
return this.staplerStatus;
}
refillStaples(): void {
console.log('📎 Refilling staples...');
this.stapleCount = 5000;
this.staplerStatus = 'ready';
console.log('✅ Stapler refilled!');
}
// Cloud implementation
uploadToCloud(document: Document, service: string): void {
if (!this.cloudAccounts.has(service)) {
throw new Error(`Not connected to ${service}`);
}
console.log(`☁️ Uploading "${document.name}" to ${service}...`);
console.log('✅ Upload complete!');
}
downloadFromCloud(documentId: string, service: string): Document {
if (!this.cloudAccounts.has(service)) {
throw new Error(`Not connected to ${service}`);
}
console.log(`☁️ Downloading ${documentId} from ${service}...`);
return {
id: documentId,
name: `Downloaded_${documentId}`,
content: 'Downloaded content...',
pages: Math.floor(Math.random() * 10) + 1
};
}
linkCloudAccount(service: string, credentials: any): void {
console.log(`☁️ Linking ${service} account...`);
this.cloudAccounts.set(service, credentials);
console.log(`✅ ${service} account linked!`);
}
}
// 🎯 Usage examples showing flexibility
function printDocument(printer: Printer, doc: Document): void {
if (printer.getInkLevel() < 10) {
console.log('⚠️ Low ink warning!');
}
printer.print(doc);
}
function scanAndPrint(device: Printer & Scanner): void {
const scanned = device.scan({
resolution: 300,
colorMode: 'color',
format: 'pdf'
});
device.print(scanned);
}
function networkPrint(device: Printer & NetworkDevice, doc: Document): void {
if (device.getNetworkStatus() !== 'connected') {
console.log('🌐 Connecting to network first...');
device.connect('Office_WiFi', 'password123');
}
console.log(`🌐 Printing via network (IP: ${device.getIPAddress()})`);
device.print(doc);
}
// 🚀 Demo
const testDoc: Document = {
id: 'doc1',
name: 'Quarterly Report',
content: 'Financial data...',
pages: 10
};
console.log('=== Basic Printer ===');
const basicPrinter = new BasicPrinter();
printDocument(basicPrinter, testDoc);
console.log('\n=== Multifunction Printer ===');
const mfp = new MultifunctionPrinter();
mfp.connect('Office_WiFi', 'password123');
scanAndPrint(mfp);
networkPrint(mfp, testDoc);
console.log('\n=== Enterprise Printer ===');
const enterprise = new EnterprisePrinter();
enterprise.linkCloudAccount('GoogleDrive', { token: 'abc123' });
enterprise.uploadToCloud(testDoc, 'GoogleDrive');
enterprise.sendFax('+1234567890', testDoc);
enterprise.staple(5);
🏪 E-commerce System
Let’s apply ISP to an e-commerce platform:
// 🏪 E-commerce interfaces following ISP
// 🛍️ Product interfaces
interface Viewable {
display(): ProductView;
getImages(): string[];
getDescription(): string;
}
interface Purchasable {
price: number;
currency: string;
addToCart(quantity: number): CartItem;
isInStock(): boolean;
getStockLevel(): number;
}
interface Discountable {
applyDiscount(code: string): number;
getActiveDiscounts(): Discount[];
calculateFinalPrice(): number;
}
interface Reviewable {
getReviews(): Review[];
addReview(review: Review): void;
getAverageRating(): number;
}
interface Shippable {
weight: number;
dimensions: Dimensions;
calculateShipping(destination: Address): ShippingCost;
getShippingMethods(): ShippingMethod[];
}
interface Digital {
downloadUrl: string;
fileSize: number;
generateDownloadLink(orderId: string): string;
getDownloadExpiry(): Date;
}
interface Trackable {
track(event: TrackingEvent): void;
getAnalytics(): ProductAnalytics;
}
interface Wishlistable {
addToWishlist(userId: string): void;
removeFromWishlist(userId: string): void;
getWishlistCount(): number;
}
// 🎯 Supporting types
interface ProductView {
id: string;
name: string;
images: string[];
description: string;
price: number;
currency: string;
}
interface CartItem {
productId: string;
productName: string;
quantity: number;
unitPrice: number;
totalPrice: number;
}
interface Discount {
code: string;
percentage: number;
validUntil: Date;
}
interface Review {
id: string;
userId: string;
rating: number;
comment: string;
timestamp: Date;
}
interface Dimensions {
length: number;
width: number;
height: number;
unit: 'cm' | 'inch';
}
interface Address {
street: string;
city: string;
state: string;
zipCode: string;
country: string;
}
interface ShippingCost {
method: string;
cost: number;
estimatedDays: number;
}
interface ShippingMethod {
name: string;
carrier: string;
estimatedDays: number;
basePrice: number;
}
interface TrackingEvent {
event: 'view' | 'add_to_cart' | 'purchase' | 'review';
timestamp: Date;
userId?: string;
metadata?: any;
}
interface ProductAnalytics {
views: number;
purchases: number;
cartAdds: number;
conversionRate: number;
}
// 📦 Physical product implementation
class PhysicalProduct implements Viewable, Purchasable, Shippable, Reviewable, Wishlistable, Trackable {
id: string;
name: string;
description: string;
images: string[];
price: number;
currency: string = 'USD';
weight: number;
dimensions: Dimensions;
stock: number;
private reviews: Review[] = [];
private wishlistUsers: Set<string> = new Set();
private analytics: ProductAnalytics = {
views: 0,
purchases: 0,
cartAdds: 0,
conversionRate: 0
};
constructor(
id: string,
name: string,
description: string,
images: string[],
price: number,
weight: number,
dimensions: Dimensions,
stock: number
) {
this.id = id;
this.name = name;
this.description = description;
this.images = images;
this.price = price;
this.weight = weight;
this.dimensions = dimensions;
this.stock = stock;
}
// Viewable implementation
display(): ProductView {
this.track({ event: 'view', timestamp: new Date() });
return {
id: this.id,
name: this.name,
images: this.images,
description: this.description,
price: this.price,
currency: this.currency
};
}
getImages(): string[] {
return [...this.images];
}
getDescription(): string {
return this.description;
}
// Purchasable implementation
addToCart(quantity: number): CartItem {
if (quantity > this.stock) {
throw new Error(`Only ${this.stock} items available`);
}
this.track({
event: 'add_to_cart',
timestamp: new Date(),
metadata: { quantity }
});
return {
productId: this.id,
productName: this.name,
quantity,
unitPrice: this.price,
totalPrice: this.price * quantity
};
}
isInStock(): boolean {
return this.stock > 0;
}
getStockLevel(): number {
return this.stock;
}
// Shippable implementation
calculateShipping(destination: Address): ShippingCost {
const baseRate = 5;
const weightRate = this.weight * 0.5;
const distanceMultiplier = destination.country === 'USA' ? 1 : 2.5;
return {
method: 'Standard',
cost: (baseRate + weightRate) * distanceMultiplier,
estimatedDays: destination.country === 'USA' ? 5 : 14
};
}
getShippingMethods(): ShippingMethod[] {
return [
{ name: 'Standard', carrier: 'USPS', estimatedDays: 5, basePrice: 5 },
{ name: 'Express', carrier: 'FedEx', estimatedDays: 2, basePrice: 15 },
{ name: 'Overnight', carrier: 'UPS', estimatedDays: 1, basePrice: 30 }
];
}
// Reviewable implementation
getReviews(): Review[] {
return [...this.reviews];
}
addReview(review: Review): void {
this.reviews.push(review);
this.track({
event: 'review',
timestamp: new Date(),
userId: review.userId,
metadata: { rating: review.rating }
});
}
getAverageRating(): number {
if (this.reviews.length === 0) return 0;
const sum = this.reviews.reduce((acc, review) => acc + review.rating, 0);
return sum / this.reviews.length;
}
// Wishlistable implementation
addToWishlist(userId: string): void {
this.wishlistUsers.add(userId);
console.log(`❤️ User ${userId} added ${this.name} to wishlist`);
}
removeFromWishlist(userId: string): void {
this.wishlistUsers.delete(userId);
console.log(`💔 User ${userId} removed ${this.name} from wishlist`);
}
getWishlistCount(): number {
return this.wishlistUsers.size;
}
// Trackable implementation
track(event: TrackingEvent): void {
switch (event.event) {
case 'view':
this.analytics.views++;
break;
case 'add_to_cart':
this.analytics.cartAdds++;
break;
case 'purchase':
this.analytics.purchases++;
break;
}
this.analytics.conversionRate =
this.analytics.views > 0
? (this.analytics.purchases / this.analytics.views) * 100
: 0;
}
getAnalytics(): ProductAnalytics {
return { ...this.analytics };
}
}
// 💾 Digital product implementation
class DigitalProduct implements Viewable, Purchasable, Digital, Reviewable, Trackable {
id: string;
name: string;
description: string;
images: string[];
price: number;
currency: string = 'USD';
downloadUrl: string;
fileSize: number;
private reviews: Review[] = [];
private analytics: ProductAnalytics = {
views: 0,
purchases: 0,
cartAdds: 0,
conversionRate: 0
};
constructor(
id: string,
name: string,
description: string,
images: string[],
price: number,
downloadUrl: string,
fileSize: number
) {
this.id = id;
this.name = name;
this.description = description;
this.images = images;
this.price = price;
this.downloadUrl = downloadUrl;
this.fileSize = fileSize;
}
// Viewable implementation
display(): ProductView {
this.track({ event: 'view', timestamp: new Date() });
return {
id: this.id,
name: this.name,
images: this.images,
description: this.description,
price: this.price,
currency: this.currency
};
}
getImages(): string[] {
return [...this.images];
}
getDescription(): string {
return this.description;
}
// Purchasable implementation
addToCart(quantity: number): CartItem {
this.track({
event: 'add_to_cart',
timestamp: new Date(),
metadata: { quantity }
});
return {
productId: this.id,
productName: this.name,
quantity,
unitPrice: this.price,
totalPrice: this.price * quantity
};
}
isInStock(): boolean {
return true; // Digital products are always "in stock"
}
getStockLevel(): number {
return Infinity; // Unlimited digital copies
}
// Digital implementation
generateDownloadLink(orderId: string): string {
const token = Buffer.from(`${orderId}:${this.id}`).toString('base64');
return `${this.downloadUrl}?token=${token}`;
}
getDownloadExpiry(): Date {
const expiry = new Date();
expiry.setDate(expiry.getDate() + 30); // 30 days expiry
return expiry;
}
// Reviewable implementation
getReviews(): Review[] {
return [...this.reviews];
}
addReview(review: Review): void {
this.reviews.push(review);
this.track({
event: 'review',
timestamp: new Date(),
userId: review.userId,
metadata: { rating: review.rating }
});
}
getAverageRating(): number {
if (this.reviews.length === 0) return 0;
const sum = this.reviews.reduce((acc, review) => acc + review.rating, 0);
return sum / this.reviews.length;
}
// Trackable implementation
track(event: TrackingEvent): void {
switch (event.event) {
case 'view':
this.analytics.views++;
break;
case 'add_to_cart':
this.analytics.cartAdds++;
break;
case 'purchase':
this.analytics.purchases++;
break;
}
this.analytics.conversionRate =
this.analytics.views > 0
? (this.analytics.purchases / this.analytics.views) * 100
: 0;
}
getAnalytics(): ProductAnalytics {
return { ...this.analytics };
}
}
// 🎫 Subscription product with discounts
class SubscriptionProduct extends DigitalProduct implements Discountable {
private discounts: Map<string, Discount> = new Map();
private billingPeriod: 'monthly' | 'yearly';
constructor(
id: string,
name: string,
description: string,
images: string[],
price: number,
billingPeriod: 'monthly' | 'yearly'
) {
super(
id,
name,
description,
images,
price,
'subscription_portal',
0
);
this.billingPeriod = billingPeriod;
}
// Discountable implementation
applyDiscount(code: string): number {
const discount = this.discounts.get(code);
if (!discount) {
throw new Error('Invalid discount code');
}
if (discount.validUntil < new Date()) {
throw new Error('Discount code expired');
}
return this.price * (1 - discount.percentage / 100);
}
getActiveDiscounts(): Discount[] {
const now = new Date();
return Array.from(this.discounts.values())
.filter(d => d.validUntil > now);
}
calculateFinalPrice(): number {
const activeDiscounts = this.getActiveDiscounts();
if (activeDiscounts.length === 0) {
return this.price;
}
// Apply the best discount
const bestDiscount = activeDiscounts.reduce((best, current) =>
current.percentage > best.percentage ? current : best
);
return this.price * (1 - bestDiscount.percentage / 100);
}
addDiscount(discount: Discount): void {
this.discounts.set(discount.code, discount);
console.log(`🎟️ Added discount ${discount.code} (${discount.percentage}% off)`);
}
getBillingPeriod(): string {
return this.billingPeriod;
}
}
// 🎯 Usage showing interface segregation benefits
function displayProduct(product: Viewable): void {
const view = product.display();
console.log(`📦 ${view.name}: ${view.description.substring(0, 50)}...`);
console.log(`💰 Price: ${view.currency} ${view.price}`);
}
function shipProduct(product: Shippable, destination: Address): void {
const shipping = product.calculateShipping(destination);
console.log(`🚚 Shipping to ${destination.city}, ${destination.country}`);
console.log(`💵 Cost: $${shipping.cost} (${shipping.estimatedDays} days)`);
}
function processDigitalPurchase(product: Digital & Purchasable, orderId: string): void {
if (!product.isInStock()) {
throw new Error('Product not available');
}
const downloadLink = product.generateDownloadLink(orderId);
const expiry = product.getDownloadExpiry();
console.log(`💾 Digital purchase processed!`);
console.log(`🔗 Download: ${downloadLink}`);
console.log(`⏰ Expires: ${expiry.toLocaleDateString()}`);
}
// 🚀 Demo
console.log('=== Physical Product ===');
const laptop = new PhysicalProduct(
'laptop001',
'Gaming Laptop Pro',
'High-performance gaming laptop with RTX graphics',
['laptop1.jpg', 'laptop2.jpg'],
1299.99,
2.5,
{ length: 35, width: 25, height: 2, unit: 'cm' },
10
);
displayProduct(laptop);
const cartItem = laptop.addToCart(1);
console.log(`🛒 Added to cart: ${cartItem.productName} x${cartItem.quantity}`);
shipProduct(laptop, {
street: '123 Main St',
city: 'New York',
state: 'NY',
zipCode: '10001',
country: 'USA'
});
console.log('\n=== Digital Product ===');
const ebook = new DigitalProduct(
'ebook001',
'TypeScript Mastery',
'Complete guide to mastering TypeScript',
['cover.jpg'],
39.99,
'https://downloads.example.com/ts-mastery',
15000000
);
displayProduct(ebook);
processDigitalPurchase(ebook, 'order123');
console.log('\n=== Subscription Product ===');
const subscription = new SubscriptionProduct(
'sub001',
'Premium Membership',
'Access to all premium content and features',
['premium.jpg'],
19.99,
'monthly'
);
subscription.addDiscount({
code: 'SAVE20',
percentage: 20,
validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
});
displayProduct(subscription);
console.log(`💳 Regular price: $${subscription.price}/${subscription.getBillingPeriod()}`);
console.log(`🎯 With discount: $${subscription.applyDiscount('SAVE20')}/${subscription.getBillingPeriod()}`);
⚠️ Common Pitfalls and Solutions
😱 Pitfall 1: Over-Segregation
// ❌ Wrong - too many tiny interfaces
interface HasId { id: string; }
interface HasName { name: string; }
interface HasEmail { email: string; }
interface HasPhone { phone: string; }
// ... 20 more single-property interfaces
class User implements HasId, HasName, HasEmail, HasPhone /* ... */ {
// This becomes unmanageable!
}
// ✅ Better - group related properties
interface Identifiable {
id: string;
uuid?: string;
}
interface ContactInfo {
email: string;
phone?: string;
alternateEmail?: string;
}
interface PersonalInfo {
name: string;
dateOfBirth?: Date;
gender?: string;
}
class User implements Identifiable, ContactInfo, PersonalInfo {
// Much cleaner and logical grouping
}
🤯 Pitfall 2: Wrong Abstraction Level
// ❌ Wrong - mixing abstraction levels
interface Vehicle {
startEngine(): void;
stopEngine(): void;
openTrunk(): void; // Not all vehicles have trunks!
foldWings(): void; // Not all vehicles have wings!
}
// ✅ Better - proper abstraction levels
interface Vehicle {
start(): void;
stop(): void;
}
interface Car extends Vehicle {
openTrunk(): void;
honk(): void;
}
interface Airplane extends Vehicle {
foldWings(): void;
engageAutopilot(): void;
}
interface Boat extends Vehicle {
dropAnchor(): void;
soundHorn(): void;
}
🛠️ Best Practices
🎯 ISP Guidelines
- Single Purpose 🎯: Each interface should have one clear purpose
- Client-Focused 👥: Design interfaces based on client needs
- Prefer Many to Few 🔢: Multiple small interfaces > one large interface
- Cohesion 🤝: Group related methods together
// 🌟 Example of well-designed interfaces
interface DataReader<T> {
read(id: string): Promise<T>;
readAll(): Promise<T[]>;
exists(id: string): Promise<boolean>;
}
interface DataWriter<T> {
create(data: Omit<T, 'id'>): Promise<T>;
update(id: string, data: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
}
interface DataSearcher<T> {
search(query: string): Promise<T[]>;
filter(predicate: (item: T) => boolean): Promise<T[]>;
}
interface DataAggregator<T> {
count(): Promise<number>;
groupBy<K extends keyof T>(field: K): Promise<Map<T[K], T[]>>;
aggregate<R>(operation: (items: T[]) => R): Promise<R>;
}
// Classes can implement only what they need
class ReadOnlyRepository<T> implements DataReader<T>, DataSearcher<T> {
// Implementation for read and search only
}
class FullRepository<T> implements DataReader<T>, DataWriter<T>, DataSearcher<T>, DataAggregator<T> {
// Full CRUD + search + aggregation
}
class CacheRepository<T> implements DataReader<T>, DataWriter<T> {
// Simple cache with read/write
}
🧪 Hands-On Exercise
🎯 Challenge: Design a Media Player System
Create a media player system that follows ISP:
📋 Requirements:
- ✅ Support different media types (audio, video, streaming)
- 🎨 Different playback capabilities
- 🎯 Various quality settings
- 📊 Analytics and tracking
- 🔧 Plugin system for extensions
🚀 Bonus Points:
- Add playlist management
- Implement subtitle support
- Create equalizer interface
💡 Solution
🔍 Click to see solution
// 🎯 Media player interfaces following ISP
// 🎵 Core playback interfaces
interface Playable {
play(): void;
pause(): void;
stop(): void;
isPlaying(): boolean;
}
interface Seekable {
seek(position: number): void;
getCurrentPosition(): number;
getDuration(): number;
}
interface VolumeControl {
setVolume(level: number): void;
getVolume(): number;
mute(): void;
unmute(): void;
isMuted(): boolean;
}
interface SpeedControl {
setPlaybackSpeed(speed: number): void;
getPlaybackSpeed(): number;
getSupportedSpeeds(): number[];
}
// 🎥 Video-specific interfaces
interface VideoPlayback {
setVideoQuality(quality: VideoQuality): void;
getVideoQuality(): VideoQuality;
getAvailableQualities(): VideoQuality[];
enterFullscreen(): void;
exitFullscreen(): void;
isFullscreen(): boolean;
}
interface SubtitleSupport {
loadSubtitles(url: string): void;
enableSubtitles(): void;
disableSubtitles(): void;
setSubtitleTrack(trackId: number): void;
getSubtitleTracks(): SubtitleTrack[];
}
interface PictureInPicture {
enterPiP(): void;
exitPiP(): void;
isPiPSupported(): boolean;
isPiPActive(): boolean;
}
// 🎵 Audio-specific interfaces
interface AudioEnhancement {
setEqualizer(preset: EqualizerPreset): void;
getEqualizer(): EqualizerPreset;
getEqualizerPresets(): EqualizerPreset[];
setBassBoost(level: number): void;
setTrebleBoost(level: number): void;
}
interface AudioVisualization {
enableVisualizer(type: VisualizerType): void;
disableVisualizer(): void;
getVisualizerTypes(): VisualizerType[];
getFrequencyData(): Uint8Array;
}
// 📡 Streaming interfaces
interface StreamingCapable {
setStreamQuality(quality: StreamQuality): void;
getStreamQuality(): StreamQuality;
getBufferLevel(): number;
preload(): void;
}
interface LiveStreamSupport {
isLive(): boolean;
goToLive(): void;
getLatency(): number;
setLowLatencyMode(enabled: boolean): void;
}
interface AdaptiveBitrate {
enableABR(): void;
disableABR(): void;
isABREnabled(): boolean;
getCurrentBitrate(): number;
getAvailableBitrates(): number[];
}
// 📋 Playlist interfaces
interface PlaylistManagement {
loadPlaylist(playlist: Playlist): void;
next(): void;
previous(): void;
shuffle(): void;
setRepeatMode(mode: RepeatMode): void;
getCurrentTrack(): Track;
getPlaylist(): Playlist;
}
interface QueueManagement {
addToQueue(track: Track): void;
removeFromQueue(trackId: string): void;
clearQueue(): void;
getQueue(): Track[];
moveInQueue(trackId: string, newPosition: number): void;
}
// 📊 Analytics interfaces
interface PlaybackAnalytics {
trackEvent(event: AnalyticsEvent): void;
getPlaybackStats(): PlaybackStats;
getSessionDuration(): number;
}
interface QualityAnalytics {
getQualityMetrics(): QualityMetrics;
getBufferingEvents(): BufferingEvent[];
getErrorRate(): number;
}
// 🔌 Plugin interfaces
interface PluginHost {
loadPlugin(plugin: MediaPlugin): void;
unloadPlugin(pluginId: string): void;
getLoadedPlugins(): MediaPlugin[];
enablePlugin(pluginId: string): void;
disablePlugin(pluginId: string): void;
}
interface MediaPlugin {
id: string;
name: string;
version: string;
initialize(player: any): void;
destroy(): void;
}
// 📝 Supporting types
type VideoQuality = '144p' | '240p' | '360p' | '480p' | '720p' | '1080p' | '1440p' | '4K' | '8K';
type StreamQuality = 'auto' | 'low' | 'medium' | 'high' | 'ultra';
type RepeatMode = 'none' | 'one' | 'all';
type VisualizerType = 'bars' | 'wave' | 'circular' | 'spectrum';
interface SubtitleTrack {
id: number;
language: string;
label: string;
kind: 'subtitles' | 'captions' | 'descriptions';
}
interface EqualizerPreset {
name: string;
frequencies: number[];
gains: number[];
}
interface Track {
id: string;
title: string;
artist?: string;
album?: string;
duration: number;
url: string;
thumbnail?: string;
}
interface Playlist {
id: string;
name: string;
tracks: Track[];
createdAt: Date;
}
interface AnalyticsEvent {
type: 'play' | 'pause' | 'seek' | 'quality_change' | 'error';
timestamp: Date;
metadata?: any;
}
interface PlaybackStats {
totalPlayTime: number;
averageSessionLength: number;
completionRate: number;
mostPlayedContent: string[];
}
interface QualityMetrics {
averageBitrate: number;
droppedFrames: number;
bufferingRatio: number;
startupTime: number;
}
interface BufferingEvent {
timestamp: Date;
duration: number;
reason?: string;
}
// 🎵 Audio player implementation
class AudioPlayer implements Playable, Seekable, VolumeControl, SpeedControl, PlaylistManagement, AudioEnhancement {
private isPlayingState = false;
private currentPosition = 0;
private duration = 0;
private volume = 100;
private muted = false;
private playbackSpeed = 1;
private playlist: Playlist | null = null;
private currentTrackIndex = 0;
private repeatMode: RepeatMode = 'none';
private equalizer: EqualizerPreset = {
name: 'Flat',
frequencies: [60, 230, 910, 3600, 14000],
gains: [0, 0, 0, 0, 0]
};
// Playable implementation
play(): void {
console.log('▶️ Playing audio...');
this.isPlayingState = true;
}
pause(): void {
console.log('⏸️ Pausing audio...');
this.isPlayingState = false;
}
stop(): void {
console.log('⏹️ Stopping audio...');
this.isPlayingState = false;
this.currentPosition = 0;
}
isPlaying(): boolean {
return this.isPlayingState;
}
// Seekable implementation
seek(position: number): void {
console.log(`⏩ Seeking to ${position}s`);
this.currentPosition = Math.max(0, Math.min(position, this.duration));
}
getCurrentPosition(): number {
return this.currentPosition;
}
getDuration(): number {
return this.duration;
}
// VolumeControl implementation
setVolume(level: number): void {
this.volume = Math.max(0, Math.min(100, level));
console.log(`🔊 Volume: ${this.volume}%`);
}
getVolume(): number {
return this.volume;
}
mute(): void {
this.muted = true;
console.log('🔇 Muted');
}
unmute(): void {
this.muted = false;
console.log('🔊 Unmuted');
}
isMuted(): boolean {
return this.muted;
}
// SpeedControl implementation
setPlaybackSpeed(speed: number): void {
this.playbackSpeed = speed;
console.log(`⚡ Playback speed: ${speed}x`);
}
getPlaybackSpeed(): number {
return this.playbackSpeed;
}
getSupportedSpeeds(): number[] {
return [0.5, 0.75, 1, 1.25, 1.5, 2];
}
// PlaylistManagement implementation
loadPlaylist(playlist: Playlist): void {
this.playlist = playlist;
this.currentTrackIndex = 0;
console.log(`📋 Loaded playlist: ${playlist.name} (${playlist.tracks.length} tracks)`);
}
next(): void {
if (!this.playlist) return;
this.currentTrackIndex = (this.currentTrackIndex + 1) % this.playlist.tracks.length;
console.log(`⏭️ Next track: ${this.getCurrentTrack().title}`);
}
previous(): void {
if (!this.playlist) return;
this.currentTrackIndex = this.currentTrackIndex === 0
? this.playlist.tracks.length - 1
: this.currentTrackIndex - 1;
console.log(`⏮️ Previous track: ${this.getCurrentTrack().title}`);
}
shuffle(): void {
if (!this.playlist) return;
// Fisher-Yates shuffle
const tracks = [...this.playlist.tracks];
for (let i = tracks.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[tracks[i], tracks[j]] = [tracks[j], tracks[i]];
}
this.playlist.tracks = tracks;
console.log('🔀 Playlist shuffled');
}
setRepeatMode(mode: RepeatMode): void {
this.repeatMode = mode;
console.log(`🔁 Repeat mode: ${mode}`);
}
getCurrentTrack(): Track {
if (!this.playlist || this.playlist.tracks.length === 0) {
throw new Error('No track loaded');
}
return this.playlist.tracks[this.currentTrackIndex];
}
getPlaylist(): Playlist {
if (!this.playlist) {
throw new Error('No playlist loaded');
}
return this.playlist;
}
// AudioEnhancement implementation
setEqualizer(preset: EqualizerPreset): void {
this.equalizer = preset;
console.log(`🎚️ Equalizer: ${preset.name}`);
}
getEqualizer(): EqualizerPreset {
return this.equalizer;
}
getEqualizerPresets(): EqualizerPreset[] {
return [
{ name: 'Flat', frequencies: [60, 230, 910, 3600, 14000], gains: [0, 0, 0, 0, 0] },
{ name: 'Rock', frequencies: [60, 230, 910, 3600, 14000], gains: [5, 3, 0, 2, 4] },
{ name: 'Jazz', frequencies: [60, 230, 910, 3600, 14000], gains: [3, 2, 0, 2, 3] },
{ name: 'Classical', frequencies: [60, 230, 910, 3600, 14000], gains: [0, 0, 0, 2, 3] }
];
}
setBassBoost(level: number): void {
console.log(`🔊 Bass boost: ${level}`);
}
setTrebleBoost(level: number): void {
console.log(`🔊 Treble boost: ${level}`);
}
}
// 🎥 Video player implementation
class VideoPlayer extends AudioPlayer implements VideoPlayback, SubtitleSupport, PictureInPicture {
private videoQuality: VideoQuality = '720p';
private fullscreen = false;
private subtitlesEnabled = false;
private subtitleTracks: SubtitleTrack[] = [];
private currentSubtitleTrack = 0;
private pipActive = false;
// VideoPlayback implementation
setVideoQuality(quality: VideoQuality): void {
this.videoQuality = quality;
console.log(`📺 Video quality: ${quality}`);
}
getVideoQuality(): VideoQuality {
return this.videoQuality;
}
getAvailableQualities(): VideoQuality[] {
return ['360p', '480p', '720p', '1080p'];
}
enterFullscreen(): void {
this.fullscreen = true;
console.log('📺 Entered fullscreen');
}
exitFullscreen(): void {
this.fullscreen = false;
console.log('📺 Exited fullscreen');
}
isFullscreen(): boolean {
return this.fullscreen;
}
// SubtitleSupport implementation
loadSubtitles(url: string): void {
console.log(`📝 Loading subtitles from: ${url}`);
this.subtitleTracks.push({
id: this.subtitleTracks.length,
language: 'en',
label: 'English',
kind: 'subtitles'
});
}
enableSubtitles(): void {
this.subtitlesEnabled = true;
console.log('📝 Subtitles enabled');
}
disableSubtitles(): void {
this.subtitlesEnabled = false;
console.log('📝 Subtitles disabled');
}
setSubtitleTrack(trackId: number): void {
this.currentSubtitleTrack = trackId;
console.log(`📝 Subtitle track: ${trackId}`);
}
getSubtitleTracks(): SubtitleTrack[] {
return [...this.subtitleTracks];
}
// PictureInPicture implementation
enterPiP(): void {
if (!this.isPiPSupported()) {
throw new Error('PiP not supported');
}
this.pipActive = true;
console.log('🖼️ Entered Picture-in-Picture');
}
exitPiP(): void {
this.pipActive = false;
console.log('🖼️ Exited Picture-in-Picture');
}
isPiPSupported(): boolean {
return true; // Simplified
}
isPiPActive(): boolean {
return this.pipActive;
}
}
// 📡 Streaming player implementation
class StreamingPlayer extends VideoPlayer implements StreamingCapable, LiveStreamSupport, AdaptiveBitrate, PlaybackAnalytics {
private streamQuality: StreamQuality = 'auto';
private bufferLevel = 0;
private isLiveStream = false;
private abrEnabled = true;
private currentBitrate = 5000;
private analytics: AnalyticsEvent[] = [];
private sessionStart = new Date();
// StreamingCapable implementation
setStreamQuality(quality: StreamQuality): void {
this.streamQuality = quality;
console.log(`📡 Stream quality: ${quality}`);
this.trackEvent({
type: 'quality_change',
timestamp: new Date(),
metadata: { quality }
});
}
getStreamQuality(): StreamQuality {
return this.streamQuality;
}
getBufferLevel(): number {
return this.bufferLevel;
}
preload(): void {
console.log('⏳ Preloading stream...');
this.bufferLevel = 30; // 30 seconds buffered
}
// LiveStreamSupport implementation
isLive(): boolean {
return this.isLiveStream;
}
goToLive(): void {
if (!this.isLiveStream) {
throw new Error('Not a live stream');
}
console.log('🔴 Going to live edge');
this.seek(this.getDuration());
}
getLatency(): number {
return this.isLiveStream ? 5.2 : 0; // 5.2 seconds latency
}
setLowLatencyMode(enabled: boolean): void {
console.log(`⚡ Low latency mode: ${enabled ? 'ON' : 'OFF'}`);
}
// AdaptiveBitrate implementation
enableABR(): void {
this.abrEnabled = true;
console.log('📊 Adaptive bitrate enabled');
}
disableABR(): void {
this.abrEnabled = false;
console.log('📊 Adaptive bitrate disabled');
}
isABREnabled(): boolean {
return this.abrEnabled;
}
getCurrentBitrate(): number {
return this.currentBitrate;
}
getAvailableBitrates(): number[] {
return [500, 1000, 2500, 5000, 8000, 12000]; // kbps
}
// PlaybackAnalytics implementation
trackEvent(event: AnalyticsEvent): void {
this.analytics.push(event);
console.log(`📊 Analytics: ${event.type} at ${event.timestamp.toISOString()}`);
}
getPlaybackStats(): PlaybackStats {
const playEvents = this.analytics.filter(e => e.type === 'play').length;
const totalTime = (new Date().getTime() - this.sessionStart.getTime()) / 1000;
return {
totalPlayTime: totalTime,
averageSessionLength: totalTime / Math.max(playEvents, 1),
completionRate: 0.85, // Simplified
mostPlayedContent: ['Video 1', 'Video 2']
};
}
getSessionDuration(): number {
return (new Date().getTime() - this.sessionStart.getTime()) / 1000;
}
// Override play to track analytics
play(): void {
super.play();
this.trackEvent({ type: 'play', timestamp: new Date() });
}
pause(): void {
super.pause();
this.trackEvent({ type: 'pause', timestamp: new Date() });
}
seek(position: number): void {
super.seek(position);
this.trackEvent({
type: 'seek',
timestamp: new Date(),
metadata: { position }
});
}
}
// 🔌 Plugin system
class PluginManager implements PluginHost {
private plugins = new Map<string, MediaPlugin>();
private enabledPlugins = new Set<string>();
loadPlugin(plugin: MediaPlugin): void {
this.plugins.set(plugin.id, plugin);
console.log(`🔌 Loaded plugin: ${plugin.name} v${plugin.version}`);
}
unloadPlugin(pluginId: string): void {
const plugin = this.plugins.get(pluginId);
if (plugin) {
plugin.destroy();
this.plugins.delete(pluginId);
this.enabledPlugins.delete(pluginId);
console.log(`🔌 Unloaded plugin: ${pluginId}`);
}
}
getLoadedPlugins(): MediaPlugin[] {
return Array.from(this.plugins.values());
}
enablePlugin(pluginId: string): void {
if (this.plugins.has(pluginId)) {
this.enabledPlugins.add(pluginId);
console.log(`✅ Enabled plugin: ${pluginId}`);
}
}
disablePlugin(pluginId: string): void {
this.enabledPlugins.delete(pluginId);
console.log(`❌ Disabled plugin: ${pluginId}`);
}
}
// 🎯 Usage examples showing ISP benefits
function basicPlayback(player: Playable & VolumeControl): void {
player.setVolume(80);
player.play();
setTimeout(() => {
player.pause();
}, 5000);
}
function enhancedAudio(player: Playable & AudioEnhancement): void {
const presets = player.getEqualizerPresets();
player.setEqualizer(presets.find(p => p.name === 'Rock')!);
player.setBassBoost(3);
player.play();
}
function videoStreaming(player: VideoPlayback & StreamingCapable & AdaptiveBitrate): void {
player.enableABR();
player.setStreamQuality('auto');
player.preload();
const qualities = player.getAvailableQualities();
console.log(`📺 Available qualities: ${qualities.join(', ')}`);
if (player.getBufferLevel() > 10) {
player.setVideoQuality('1080p');
}
}
function liveStreamViewing(player: LiveStreamSupport & Playable): void {
if (player.isLive()) {
player.setLowLatencyMode(true);
player.goToLive();
console.log(`📡 Live stream latency: ${player.getLatency()}s`);
}
player.play();
}
// 🚀 Demo
console.log('=== Audio Player Demo ===');
const audioPlayer = new AudioPlayer();
const audioPlaylist: Playlist = {
id: 'playlist1',
name: 'My Favorites',
tracks: [
{ id: '1', title: 'Song 1', artist: 'Artist A', duration: 180, url: 'song1.mp3' },
{ id: '2', title: 'Song 2', artist: 'Artist B', duration: 210, url: 'song2.mp3' },
{ id: '3', title: 'Song 3', artist: 'Artist C', duration: 195, url: 'song3.mp3' }
],
createdAt: new Date()
};
audioPlayer.loadPlaylist(audioPlaylist);
basicPlayback(audioPlayer);
enhancedAudio(audioPlayer);
console.log('\n=== Video Player Demo ===');
const videoPlayer = new VideoPlayer();
videoPlayer.setVideoQuality('1080p');
videoPlayer.loadSubtitles('subtitles.vtt');
videoPlayer.enableSubtitles();
if (videoPlayer.isPiPSupported()) {
videoPlayer.enterPiP();
}
console.log('\n=== Streaming Player Demo ===');
const streamingPlayer = new StreamingPlayer();
videoStreaming(streamingPlayer);
const stats = streamingPlayer.getPlaybackStats();
console.log(`📊 Total play time: ${stats.totalPlayTime}s`);
console.log(`📊 Session duration: ${streamingPlayer.getSessionDuration()}s`);
🎓 Key Takeaways
You now understand how to apply the Interface Segregation Principle! Here’s what you’ve learned:
- ✅ Fat interfaces violate ISP and cause problems 🎯
- ✅ Segregated interfaces provide flexibility and maintainability 🔀
- ✅ Client-focused design creates better contracts 👥
- ✅ Composition enables precise implementations 🧩
- ✅ ISP is essential for professional code 🌟
Remember: No client should be forced to depend on methods it doesn’t use. Keep interfaces focused and cohesive! 🏗️
🤝 Next Steps
Congratulations! 🎉 You’ve mastered the Interface Segregation Principle!
Here’s what to do next:
- 💻 Practice with the media player exercise above
- 🏗️ Refactor existing fat interfaces in your codebase
- 📚 Move on to our next tutorial: Optional Properties in Interfaces: Flexible Contracts
- 🌟 Apply ISP to your real-world projects!
Remember: Great interfaces are like great tools - specialized, focused, and perfect for their job. Keep segregating! 🚀
Happy coding! 🎉🚀✨