+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 280 of 354

๐Ÿ“˜ Interface Segregation: Focused Interfaces

Master interface segregation: focused interfaces in TypeScript with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript ๐Ÿ“
  • TypeScript installation โšก
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand the concept fundamentals ๐ŸŽฏ
  • Apply the concept in real projects ๐Ÿ—๏ธ
  • Debug common issues ๐Ÿ›
  • Write type-safe code โœจ

๐ŸŽฏ Introduction

Have you ever been to a restaurant with a menu thatโ€™s 50 pages long? ๐Ÿ“š Itโ€™s overwhelming, right? You just want a simple burger, but you have to wade through sushi, pasta, and breakfast items to find it!

Thatโ€™s exactly what happens when we create interfaces that try to do too much in TypeScript. Today, weโ€™re going to learn about the Interface Segregation Principle (ISP) โ€“ a game-changing concept that helps us create focused, easy-to-use interfaces that make our code cleaner and more maintainable! ๐ŸŒŸ

By the end of this tutorial, youโ€™ll be creating interfaces that are as focused and purposeful as a sushi-only menu at a Japanese restaurant. Letโ€™s dive in! ๐Ÿฃ

๐Ÿ“š Understanding Interface Segregation

The Interface Segregation Principle states that โ€œclients should not be forced to depend upon interfaces they do not use.โ€

Think of it like this: imagine if your TV remote had buttons for controlling your microwave, washing machine, and car stereo. That would be confusing and unnecessary! ๐Ÿ“บ You want a remote that only has the buttons you need for watching TV.

In TypeScript, this means:

  • Creating smaller, focused interfaces instead of large, general ones ๐ŸŽฏ
  • Splitting interfaces based on specific use cases ๐Ÿ”€
  • Ensuring implementers only need to worry about relevant methods ๐Ÿ’ก

Letโ€™s see why this matters with a simple example:

// โŒ Wrong: Fat interface forcing unnecessary implementations
interface Animal {
  name: string;
  eat(): void;
  sleep(): void;
  fly(): void;     // ๐Ÿฆ… Not all animals fly!
  swim(): void;    // ๐Ÿ  Not all animals swim!
  run(): void;     // ๐ŸŒ Not all animals run!
}

// โœ… Correct: Segregated interfaces for specific capabilities
interface BasicAnimal {
  name: string;
  eat(): void;
  sleep(): void;
}

interface Flyer {
  fly(): void;
}

interface Swimmer {
  swim(): void;
}

interface Runner {
  run(): void;
}

See the difference? Now animals only implement what they actually can do! ๐ŸŽ‰

๐Ÿ”ง Basic Syntax and Usage

Letโ€™s start with the basics of creating segregated interfaces in TypeScript. The key is to think about what each consumer of your interface actually needs! ๐Ÿค”

Creating Focused Interfaces

// ๐Ÿช Let's build a payment system for different types of stores!

// โœ… Segregated interfaces for different payment methods
interface CashPayment {
  acceptCash(amount: number): void;
  giveChange(amount: number): void;
}

interface CardPayment {
  processCard(cardNumber: string, amount: number): void;
  validateCard(cardNumber: string): boolean;
}

interface DigitalPayment {
  processDigitalWallet(walletId: string, amount: number): void;
  sendReceipt(email: string): void;
}

// ๐Ÿช Now stores can implement only what they support!
class CoffeeShop implements CashPayment, CardPayment {
  acceptCash(amount: number): void {
    console.log(`โ˜• Accepting $${amount} in cash`);
  }
  
  giveChange(amount: number): void {
    console.log(`๐Ÿ’ต Here's your $${amount} change`);
  }
  
  processCard(cardNumber: string, amount: number): void {
    console.log(`๐Ÿ’ณ Processing $${amount} on card ending in ${cardNumber.slice(-4)}`);
  }
  
  validateCard(cardNumber: string): boolean {
    return cardNumber.length === 16; // ๐ŸŽฏ Simple validation
  }
}

class OnlineStore implements CardPayment, DigitalPayment {
  processCard(cardNumber: string, amount: number): void {
    console.log(`๐Ÿ›’ Processing online payment of $${amount}`);
  }
  
  validateCard(cardNumber: string): boolean {
    return cardNumber.length === 16;
  }
  
  processDigitalWallet(walletId: string, amount: number): void {
    console.log(`๐Ÿ“ฑ Processing PayPal payment of $${amount}`);
  }
  
  sendReceipt(email: string): void {
    console.log(`๐Ÿ“ง Receipt sent to ${email}`);
  }
}

Combining Interfaces

TypeScript makes it super easy to combine multiple interfaces! ๐Ÿ”—

// ๐ŸŽฎ Building a game character system!

interface Health {
  currentHealth: number;
  maxHealth: number;
  takeDamage(amount: number): void;
  heal(amount: number): void;
}

interface Magic {
  currentMana: number;
  maxMana: number;
  castSpell(spellName: string): void;
}

interface Inventory {
  items: string[];
  addItem(item: string): void;
  removeItem(item: string): void;
}

// ๐Ÿง™โ€โ™‚๏ธ A wizard has health, magic, and inventory
class Wizard implements Health, Magic, Inventory {
  currentHealth = 100;
  maxHealth = 100;
  currentMana = 50;
  maxMana = 50;
  items: string[] = ['Staff', 'Potion'];
  
  takeDamage(amount: number): void {
    this.currentHealth -= amount;
    console.log(`๐Ÿ”ฅ Wizard takes ${amount} damage! HP: ${this.currentHealth}`);
  }
  
  heal(amount: number): void {
    this.currentHealth = Math.min(this.currentHealth + amount, this.maxHealth);
    console.log(`โœจ Wizard heals for ${amount}! HP: ${this.currentHealth}`);
  }
  
  castSpell(spellName: string): void {
    console.log(`๐Ÿช„ Wizard casts ${spellName}!`);
    this.currentMana -= 10;
  }
  
  addItem(item: string): void {
    this.items.push(item);
    console.log(`๐ŸŽ’ Added ${item} to inventory`);
  }
  
  removeItem(item: string): void {
    this.items = this.items.filter(i => i !== item);
    console.log(`๐Ÿ—‘๏ธ Removed ${item} from inventory`);
  }
}

// ๐Ÿน An archer only has health and inventory (no magic!)
class Archer implements Health, Inventory {
  currentHealth = 80;
  maxHealth = 80;
  items: string[] = ['Bow', 'Arrows'];
  
  takeDamage(amount: number): void {
    this.currentHealth -= amount;
    console.log(`๐Ÿ’” Archer takes ${amount} damage! HP: ${this.currentHealth}`);
  }
  
  heal(amount: number): void {
    this.currentHealth = Math.min(this.currentHealth + amount, this.maxHealth);
    console.log(`โค๏ธ Archer heals for ${amount}! HP: ${this.currentHealth}`);
  }
  
  addItem(item: string): void {
    this.items.push(item);
    console.log(`๐ŸŽ’ Added ${item} to inventory`);
  }
  
  removeItem(item: string): void {
    this.items = this.items.filter(i => i !== item);
    console.log(`๐Ÿ—‘๏ธ Removed ${item} from inventory`);
  }
}

๐Ÿ’ก Practical Examples

Letโ€™s explore some real-world scenarios where interface segregation makes your code shine! ๐ŸŒŸ

Example 1: Smart Home Devices ๐Ÿ 

// ๐Ÿ  Building a smart home system with different device capabilities

// โœ… Segregated interfaces for device capabilities
interface PowerControl {
  turnOn(): void;
  turnOff(): void;
  isOn: boolean;
}

interface Dimmable {
  setBrightness(level: number): void;
  brightness: number;
}

interface Colorable {
  setColor(color: string): void;
  currentColor: string;
}

interface TemperatureControl {
  setTemperature(temp: number): void;
  currentTemperature: number;
}

interface Schedulable {
  setSchedule(time: Date, action: string): void;
  clearSchedule(): void;
}

// ๐Ÿ’ก Smart bulb with power, dimming, and color
class SmartBulb implements PowerControl, Dimmable, Colorable {
  isOn = false;
  brightness = 100;
  currentColor = 'white';
  
  turnOn(): void {
    this.isOn = true;
    console.log('๐Ÿ’ก Light is ON');
  }
  
  turnOff(): void {
    this.isOn = false;
    console.log('๐ŸŒ‘ Light is OFF');
  }
  
  setBrightness(level: number): void {
    this.brightness = level;
    console.log(`๐Ÿ”† Brightness set to ${level}%`);
  }
  
  setColor(color: string): void {
    this.currentColor = color;
    console.log(`๐ŸŽจ Color changed to ${color}`);
  }
}

// ๐ŸŒก๏ธ Smart thermostat with power, temperature, and scheduling
class SmartThermostat implements PowerControl, TemperatureControl, Schedulable {
  isOn = true;
  currentTemperature = 72;
  private schedules: Map<string, Date> = new Map();
  
  turnOn(): void {
    this.isOn = true;
    console.log('๐Ÿ”ฅ Thermostat is ON');
  }
  
  turnOff(): void {
    this.isOn = false;
    console.log('โ„๏ธ Thermostat is OFF');
  }
  
  setTemperature(temp: number): void {
    this.currentTemperature = temp;
    console.log(`๐ŸŒก๏ธ Temperature set to ${temp}ยฐF`);
  }
  
  setSchedule(time: Date, action: string): void {
    this.schedules.set(action, time);
    console.log(`โฐ Scheduled ${action} at ${time.toLocaleTimeString()}`);
  }
  
  clearSchedule(): void {
    this.schedules.clear();
    console.log('๐Ÿ—‘๏ธ All schedules cleared');
  }
}

// ๐Ÿ”Œ Smart plug only needs power control
class SmartPlug implements PowerControl {
  isOn = false;
  
  turnOn(): void {
    this.isOn = true;
    console.log('๐Ÿ”Œ Plug is ON');
  }
  
  turnOff(): void {
    this.isOn = false;
    console.log('โšก Plug is OFF');
  }
}

Example 2: Document Processing System ๐Ÿ“„

// ๐Ÿ“„ Building a document processing system

// โœ… Segregated interfaces for different document operations
interface Readable {
  read(): string;
  getWordCount(): number;
}

interface Writable {
  write(content: string): void;
  append(content: string): void;
}

interface Printable {
  print(): void;
  getPrintPreview(): string;
}

interface Searchable {
  search(query: string): string[];
  findAndReplace(find: string, replace: string): void;
}

interface Versionable {
  saveVersion(): void;
  getVersion(): number;
  rollback(version: number): void;
}

// ๐Ÿ“ Text document with read, write, search capabilities
class TextDocument implements Readable, Writable, Searchable {
  private content = '';
  
  read(): string {
    return this.content;
  }
  
  getWordCount(): number {
    return this.content.split(' ').filter(word => word.length > 0).length;
  }
  
  write(content: string): void {
    this.content = content;
    console.log('โœ๏ธ Document written');
  }
  
  append(content: string): void {
    this.content += content;
    console.log('โž• Content appended');
  }
  
  search(query: string): string[] {
    const matches: string[] = [];
    const lines = this.content.split('\n');
    lines.forEach((line, index) => {
      if (line.includes(query)) {
        matches.push(`Line ${index + 1}: ${line}`);
      }
    });
    return matches;
  }
  
  findAndReplace(find: string, replace: string): void {
    this.content = this.content.replaceAll(find, replace);
    console.log(`๐Ÿ”„ Replaced "${find}" with "${replace}"`);
  }
}

// ๐Ÿ–ผ๏ธ PDF document - read-only with print capabilities
class PDFDocument implements Readable, Printable {
  private content: string;
  
  constructor(content: string) {
    this.content = content;
  }
  
  read(): string {
    return this.content;
  }
  
  getWordCount(): number {
    return this.content.split(' ').filter(word => word.length > 0).length;
  }
  
  print(): void {
    console.log('๐Ÿ–จ๏ธ Printing PDF...');
  }
  
  getPrintPreview(): string {
    return `๐Ÿ“„ Preview: ${this.content.substring(0, 50)}...`;
  }
}

// ๐Ÿ”’ Archived document - read-only
class ArchivedDocument implements Readable {
  private content: string;
  
  constructor(content: string) {
    this.content = content;
  }
  
  read(): string {
    console.log('๐Ÿ”“ Reading archived document');
    return this.content;
  }
  
  getWordCount(): number {
    return this.content.split(' ').filter(word => word.length > 0).length;
  }
}

Example 3: E-commerce Product System ๐Ÿ›๏ธ

// ๐Ÿ›๏ธ Building a flexible e-commerce product system

// โœ… Segregated interfaces for product features
interface BasicProduct {
  id: string;
  name: string;
  price: number;
  description: string;
}

interface InventoryTracked {
  stock: number;
  updateStock(quantity: number): void;
  isInStock(): boolean;
}

interface Discountable {
  applyDiscount(percentage: number): void;
  originalPrice: number;
  currentDiscount: number;
}

interface Reviewable {
  reviews: Review[];
  addReview(review: Review): void;
  getAverageRating(): number;
}

interface Shippable {
  weight: number;
  dimensions: { length: number; width: number; height: number };
  calculateShipping(destination: string): number;
}

interface Digital {
  downloadUrl: string;
  fileSize: number;
  generateDownloadLink(): string;
}

interface Review {
  rating: number;
  comment: string;
  author: string;
}

// ๐Ÿ“ฑ Physical product with all features
class PhysicalProduct implements BasicProduct, InventoryTracked, Discountable, Reviewable, Shippable {
  id: string;
  name: string;
  price: number;
  description: string;
  stock: number;
  originalPrice: number;
  currentDiscount = 0;
  reviews: Review[] = [];
  weight: number;
  dimensions: { length: number; width: number; height: number };
  
  constructor(id: string, name: string, price: number, description: string, stock: number, weight: number) {
    this.id = id;
    this.name = name;
    this.price = price;
    this.originalPrice = price;
    this.description = description;
    this.stock = stock;
    this.weight = weight;
    this.dimensions = { length: 10, width: 10, height: 10 };
  }
  
  updateStock(quantity: number): void {
    this.stock += quantity;
    console.log(`๐Ÿ“ฆ Stock updated: ${this.stock} units`);
  }
  
  isInStock(): boolean {
    return this.stock > 0;
  }
  
  applyDiscount(percentage: number): void {
    this.currentDiscount = percentage;
    this.price = this.originalPrice * (1 - percentage / 100);
    console.log(`๐Ÿท๏ธ ${percentage}% discount applied! New price: $${this.price}`);
  }
  
  addReview(review: Review): void {
    this.reviews.push(review);
    console.log(`โญ New review added by ${review.author}`);
  }
  
  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;
  }
  
  calculateShipping(destination: string): number {
    const baseRate = 5;
    const weightRate = this.weight * 0.5;
    console.log(`๐Ÿ“ฆ Shipping to ${destination}: $${baseRate + weightRate}`);
    return baseRate + weightRate;
  }
}

// ๐Ÿ’พ Digital product - no inventory or shipping needed!
class DigitalProduct implements BasicProduct, Discountable, Reviewable, Digital {
  id: string;
  name: string;
  price: number;
  description: string;
  originalPrice: number;
  currentDiscount = 0;
  reviews: Review[] = [];
  downloadUrl: string;
  fileSize: number;
  
  constructor(id: string, name: string, price: number, description: string, downloadUrl: string, fileSize: number) {
    this.id = id;
    this.name = name;
    this.price = price;
    this.originalPrice = price;
    this.description = description;
    this.downloadUrl = downloadUrl;
    this.fileSize = fileSize;
  }
  
  applyDiscount(percentage: number): void {
    this.currentDiscount = percentage;
    this.price = this.originalPrice * (1 - percentage / 100);
    console.log(`๐Ÿท๏ธ ${percentage}% discount applied! New price: $${this.price}`);
  }
  
  addReview(review: Review): void {
    this.reviews.push(review);
    console.log(`โญ New review added by ${review.author}`);
  }
  
  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;
  }
  
  generateDownloadLink(): string {
    const link = `${this.downloadUrl}?token=${Date.now()}`;
    console.log(`๐Ÿ”— Download link generated: ${link}`);
    return link;
  }
}

// ๐ŸŽ Gift card - simple product with no physical attributes
class GiftCard implements BasicProduct, InventoryTracked {
  id: string;
  name: string;
  price: number;
  description: string;
  stock: number;
  
  constructor(id: string, value: number) {
    this.id = id;
    this.name = `Gift Card - $${value}`;
    this.price = value;
    this.description = 'Digital gift card';
    this.stock = 999; // ๐ŸŽ Virtually unlimited!
  }
  
  updateStock(quantity: number): void {
    this.stock += quantity;
    console.log(`๐ŸŽ Gift card stock updated`);
  }
  
  isInStock(): boolean {
    return true; // ๐ŸŽ Always available!
  }
}

๐Ÿš€ Advanced Concepts

Ready to level up? Letโ€™s explore some advanced interface segregation techniques! ๐ŸŒŸ

Interface Extension and Composition

// ๐ŸŽจ Advanced interface composition techniques

// Base interfaces
interface Entity {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

interface Timestamped {
  recordTimestamp(): void;
}

// Composed interfaces using extends
interface User extends Entity {
  username: string;
  email: string;
}

interface Admin extends User, Timestamped {
  permissions: string[];
  grantPermission(permission: string): void;
}

// ๐Ÿ”ง Using type aliases for flexible composition
type Auditable = Entity & Timestamped & {
  auditLog: string[];
  addAuditEntry(action: string): void;
};

// ๐Ÿ—๏ธ Building complex systems with interface segregation
interface CRUDOperations<T> {
  create(item: T): Promise<T>;
  read(id: string): Promise<T | null>;
  update(id: string, item: Partial<T>): Promise<T>;
  delete(id: string): Promise<boolean>;
}

interface Cacheable<T> {
  cache(key: string, value: T): void;
  getFromCache(key: string): T | null;
  clearCache(): void;
}

interface Validatable<T> {
  validate(item: T): ValidationResult;
}

interface ValidationResult {
  isValid: boolean;
  errors: string[];
}

// ๐Ÿš€ Advanced repository pattern with segregated interfaces
class UserRepository implements CRUDOperations<User>, Cacheable<User>, Validatable<User> {
  private users: Map<string, User> = new Map();
  private cache: Map<string, User> = new Map();
  
  async create(user: User): Promise<User> {
    const validation = this.validate(user);
    if (!validation.isValid) {
      throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
    }
    
    this.users.set(user.id, user);
    console.log(`โœ… User ${user.username} created`);
    return user;
  }
  
  async read(id: string): Promise<User | null> {
    // ๐Ÿš€ Check cache first
    const cached = this.getFromCache(id);
    if (cached) {
      console.log('โšก Returning cached user');
      return cached;
    }
    
    const user = this.users.get(id) || null;
    if (user) {
      this.cache(id, user);
    }
    return user;
  }
  
  async update(id: string, updates: Partial<User>): Promise<User> {
    const user = await this.read(id);
    if (!user) throw new Error('User not found');
    
    const updated = { ...user, ...updates, updatedAt: new Date() };
    this.users.set(id, updated);
    this.cache(id, updated);
    
    console.log(`๐Ÿ“ User ${id} updated`);
    return updated;
  }
  
  async delete(id: string): Promise<boolean> {
    const deleted = this.users.delete(id);
    this.cache.delete(id);
    console.log(`๐Ÿ—‘๏ธ User ${id} deleted`);
    return deleted;
  }
  
  cache(key: string, value: User): void {
    this.cache.set(key, value);
    console.log(`๐Ÿ’พ Cached user ${key}`);
  }
  
  getFromCache(key: string): User | null {
    return this.cache.get(key) || null;
  }
  
  clearCache(): void {
    this.cache.clear();
    console.log('๐Ÿงน Cache cleared');
  }
  
  validate(user: User): ValidationResult {
    const errors: string[] = [];
    
    if (!user.username || user.username.length < 3) {
      errors.push('Username must be at least 3 characters');
    }
    
    if (!user.email || !user.email.includes('@')) {
      errors.push('Invalid email address');
    }
    
    return {
      isValid: errors.length === 0,
      errors
    };
  }
}

Generic Interface Segregation

// ๐ŸŽฏ Using generics for flexible interface segregation

interface Sortable<T> {
  sort(compareFn?: (a: T, b: T) => number): void;
  items: T[];
}

interface Filterable<T> {
  filter(predicate: (item: T) => boolean): T[];
}

interface Pageable<T> {
  getPage(pageNumber: number, pageSize: number): T[];
  getTotalPages(pageSize: number): number;
}

interface Summable<T> {
  sum(selector: (item: T) => number): number;
}

// ๐Ÿ›’ Product catalog with multiple capabilities
class ProductCatalog<T extends BasicProduct> 
  implements Sortable<T>, Filterable<T>, Pageable<T> {
  
  items: T[] = [];
  
  constructor(products: T[]) {
    this.items = products;
  }
  
  sort(compareFn?: (a: T, b: T) => number): void {
    const defaultCompare = (a: T, b: T) => a.price - b.price;
    this.items.sort(compareFn || defaultCompare);
    console.log('๐Ÿ“Š Products sorted');
  }
  
  filter(predicate: (item: T) => boolean): T[] {
    return this.items.filter(predicate);
  }
  
  getPage(pageNumber: number, pageSize: number): T[] {
    const start = (pageNumber - 1) * pageSize;
    const end = start + pageSize;
    console.log(`๐Ÿ“„ Returning page ${pageNumber}`);
    return this.items.slice(start, end);
  }
  
  getTotalPages(pageSize: number): number {
    return Math.ceil(this.items.length / pageSize);
  }
}

// ๐Ÿ’ฐ Financial data with calculation capabilities
class FinancialReport implements Sortable<Transaction>, Summable<Transaction> {
  items: Transaction[] = [];
  
  constructor(transactions: Transaction[]) {
    this.items = transactions;
  }
  
  sort(compareFn?: (a: Transaction, b: Transaction) => number): void {
    const defaultCompare = (a: Transaction, b: Transaction) => 
      a.date.getTime() - b.date.getTime();
    this.items.sort(compareFn || defaultCompare);
    console.log('๐Ÿ“Š Transactions sorted by date');
  }
  
  sum(selector: (item: Transaction) => number): number {
    const total = this.items.reduce((acc, item) => acc + selector(item), 0);
    console.log(`๐Ÿ’ฐ Total calculated: $${total}`);
    return total;
  }
}

interface Transaction {
  id: string;
  amount: number;
  date: Date;
  category: string;
}

โš ๏ธ Common Pitfalls and Solutions

Letโ€™s avoid these common mistakes when implementing interface segregation! ๐Ÿ›ก๏ธ

Pitfall 1: Creating Too Many Tiny Interfaces

// โŒ Wrong: Over-segregation leads to complexity
interface HasName {
  name: string;
}

interface HasAge {
  age: number;
}

interface HasEmail {
  email: string;
}

interface HasPhone {
  phone: string;
}

// This becomes unwieldy!
class Person implements HasName, HasAge, HasEmail, HasPhone {
  name: string;
  age: number;
  email: string;
  phone: string;
  
  constructor(name: string, age: number, email: string, phone: string) {
    this.name = name;
    this.age = age;
    this.email = email;
    this.phone = phone;
  }
}

// โœ… Correct: Group related properties logically
interface PersonalInfo {
  name: string;
  age: number;
}

interface ContactInfo {
  email: string;
  phone: string;
}

class BetterPerson implements PersonalInfo, ContactInfo {
  name: string;
  age: number;
  email: string;
  phone: string;
  
  constructor(name: string, age: number, email: string, phone: string) {
    this.name = name;
    this.age = age;
    this.email = email;
    this.phone = phone;
  }
}

Pitfall 2: Violating Cohesion

// โŒ Wrong: Mixing unrelated concerns
interface UserOperations {
  login(username: string, password: string): void;
  logout(): void;
  sendEmail(to: string, message: string): void; // ๐Ÿ“ง Not user-specific!
  generateReport(): string; // ๐Ÿ“Š Not user-specific!
}

// โœ… Correct: Keep interfaces cohesive
interface Authentication {
  login(username: string, password: string): void;
  logout(): void;
  isAuthenticated(): boolean;
}

interface EmailService {
  sendEmail(to: string, message: string): void;
  sendBulkEmails(recipients: string[], message: string): void;
}

interface ReportGenerator {
  generateReport(type: string): string;
  scheduleReport(type: string, frequency: string): void;
}

Pitfall 3: Interface Duplication

// โŒ Wrong: Duplicating interface members
interface Flyable {
  speed: number;
  altitude: number;
  fly(): void;
  land(): void;
}

interface Drivable {
  speed: number; // ๐Ÿ”„ Duplication!
  accelerate(): void;
  brake(): void;
}

// โœ… Correct: Extract common interface
interface Vehicle {
  speed: number;
  currentPosition: { x: number; y: number };
}

interface Flyable extends Vehicle {
  altitude: number;
  fly(): void;
  land(): void;
}

interface Drivable extends Vehicle {
  accelerate(): void;
  brake(): void;
}

// ๐Ÿš Now a flying car can implement both without duplication!
class FlyingCar implements Flyable, Drivable {
  speed = 0;
  altitude = 0;
  currentPosition = { x: 0, y: 0 };
  
  fly(): void {
    this.altitude = 1000;
    console.log('๐Ÿš Taking off!');
  }
  
  land(): void {
    this.altitude = 0;
    console.log('๐Ÿ›ฌ Landing');
  }
  
  accelerate(): void {
    this.speed += 10;
    console.log(`๐Ÿƒ Speed: ${this.speed} mph`);
  }
  
  brake(): void {
    this.speed = Math.max(0, this.speed - 10);
    console.log(`๐Ÿ›‘ Speed: ${this.speed} mph`);
  }
}

๐Ÿ› ๏ธ Best Practices

Follow these best practices to master interface segregation! ๐ŸŒŸ

1. Design Interfaces from the Clientโ€™s Perspective

// ๐ŸŽฏ Think about what the consumer needs

// โœ… Good: Interface designed for specific use case
interface ReadOnlyStorage {
  get(key: string): string | null;
  exists(key: string): boolean;
}

interface WritableStorage extends ReadOnlyStorage {
  set(key: string, value: string): void;
  delete(key: string): void;
}

// Different consumers have different needs
class CacheReader {
  constructor(private storage: ReadOnlyStorage) {}
  
  getCachedValue(key: string): string | null {
    if (this.storage.exists(key)) {
      return this.storage.get(key);
    }
    return null;
  }
}

class CacheManager {
  constructor(private storage: WritableStorage) {}
  
  updateCache(key: string, value: string): void {
    this.storage.set(key, value);
    console.log(`๐Ÿ’พ Cache updated: ${key}`);
  }
  
  clearExpired(keys: string[]): void {
    keys.forEach(key => this.storage.delete(key));
    console.log(`๐Ÿงน Cleared ${keys.length} expired entries`);
  }
}

2. Use Interface Composition Over Fat Interfaces

// ๐Ÿ—๏ธ Build complex interfaces from simple ones

// Base capability interfaces
interface Identifiable {
  id: string;
}

interface Trackable {
  createdAt: Date;
  modifiedAt: Date;
  createdBy: string;
  modifiedBy: string;
}

interface Taggable {
  tags: string[];
  addTag(tag: string): void;
  removeTag(tag: string): void;
  hasTag(tag: string): boolean;
}

interface Publishable {
  published: boolean;
  publishedAt?: Date;
  publish(): void;
  unpublish(): void;
}

// โœ… Compose interfaces for different content types
type BlogPost = Identifiable & Trackable & Taggable & Publishable & {
  title: string;
  content: string;
  author: string;
};

type Product = Identifiable & Trackable & Taggable & {
  name: string;
  price: number;
  sku: string;
};

type InternalDocument = Identifiable & Trackable & {
  title: string;
  content: string;
  department: string;
};

3. Name Interfaces by Capability, Not Implementation

// ๐Ÿ“ Name interfaces based on what they can do

// โœ… Good: Capability-based naming
interface Sortable<T> {
  sort(compareFn?: (a: T, b: T) => number): T[];
}

interface Searchable {
  search(query: string): SearchResult[];
}

interface Exportable {
  exportToJSON(): string;
  exportToCSV(): string;
}

// โŒ Avoid: Implementation-specific naming
interface IUserRepository { // Don't use "I" prefix
  // ...
}

interface DatabaseOperations { // Too implementation-specific
  // ...
}
// ๐Ÿ”— Group cohesive operations

// โœ… Good: Related operations in one interface
interface ShoppingCart {
  items: CartItem[];
  addItem(item: CartItem): void;
  removeItem(itemId: string): void;
  updateQuantity(itemId: string, quantity: number): void;
  getTotal(): number;
  clear(): void;
}

// โœ… Separate interface for checkout process
interface Checkout {
  validateCart(cart: ShoppingCart): ValidationResult;
  calculateShipping(address: Address): number;
  calculateTax(subtotal: number, address: Address): number;
  processPayment(payment: PaymentInfo): PaymentResult;
}

interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
}

interface Address {
  street: string;
  city: string;
  state: string;
  zip: string;
}

interface PaymentInfo {
  method: 'card' | 'paypal' | 'crypto';
  details: Record<string, string>;
}

interface PaymentResult {
  success: boolean;
  transactionId?: string;
  error?: string;
}

๐Ÿงช Hands-On Exercise

Time to put your skills to the test! ๐ŸŽฏ Create a media player system using interface segregation principles.

Challenge: Build a Media Player System ๐ŸŽต

Create interfaces and classes for different types of media players (audio, video, streaming) with appropriate capabilities.

Requirements:

  1. Create segregated interfaces for different media capabilities
  2. Implement at least 3 different types of media players
  3. Each player should only implement the interfaces it needs
  4. Add a playlist manager that works with any playable media

Try it yourself first! When youโ€™re ready, check the solution below. ๐Ÿ‘‡

๐Ÿ“ Click to see the solution
// ๐ŸŽต Media Player System with Interface Segregation

// Core media interfaces
interface Playable {
  play(): void;
  pause(): void;
  stop(): void;
  isPlaying: boolean;
}

interface Seekable {
  currentTime: number;
  duration: number;
  seek(time: number): void;
}

interface VolumeControl {
  volume: number;
  mute(): void;
  unmute(): void;
  setVolume(level: number): void;
}

interface VideoControl {
  resolution: string;
  setResolution(resolution: string): void;
  enterFullscreen(): void;
  exitFullscreen(): void;
}

interface Streamable {
  bufferPercentage: number;
  connectionQuality: 'low' | 'medium' | 'high';
  adjustQuality(quality: 'auto' | 'low' | 'medium' | 'high'): void;
}

interface PlaylistSupport {
  currentTrack: number;
  playlist: MediaItem[];
  next(): void;
  previous(): void;
  shuffle(): void;
  repeat: 'none' | 'one' | 'all';
}

interface MediaItem {
  id: string;
  title: string;
  duration: number;
  url: string;
}

// ๐ŸŽต Audio Player - basic playback with volume
class AudioPlayer implements Playable, Seekable, VolumeControl {
  isPlaying = false;
  currentTime = 0;
  duration = 0;
  volume = 100;
  private muted = false;
  private item: MediaItem;
  
  constructor(item: MediaItem) {
    this.item = item;
    this.duration = item.duration;
  }
  
  play(): void {
    this.isPlaying = true;
    console.log(`๐ŸŽต Playing: ${this.item.title}`);
  }
  
  pause(): void {
    this.isPlaying = false;
    console.log('โธ๏ธ Paused');
  }
  
  stop(): void {
    this.isPlaying = false;
    this.currentTime = 0;
    console.log('โน๏ธ Stopped');
  }
  
  seek(time: number): void {
    this.currentTime = Math.min(time, this.duration);
    console.log(`โฉ Seeking to ${this.currentTime}s`);
  }
  
  mute(): void {
    this.muted = true;
    console.log('๐Ÿ”‡ Muted');
  }
  
  unmute(): void {
    this.muted = false;
    console.log('๐Ÿ”Š Unmuted');
  }
  
  setVolume(level: number): void {
    this.volume = Math.max(0, Math.min(100, level));
    console.log(`๐ŸŽš๏ธ Volume: ${this.volume}%`);
  }
}

// ๐ŸŽฌ Video Player - with video controls
class VideoPlayer implements Playable, Seekable, VolumeControl, VideoControl {
  isPlaying = false;
  currentTime = 0;
  duration = 0;
  volume = 100;
  resolution = '1080p';
  private muted = false;
  private fullscreen = false;
  private item: MediaItem;
  
  constructor(item: MediaItem) {
    this.item = item;
    this.duration = item.duration;
  }
  
  play(): void {
    this.isPlaying = true;
    console.log(`๐ŸŽฌ Playing video: ${this.item.title}`);
  }
  
  pause(): void {
    this.isPlaying = false;
    console.log('โธ๏ธ Video paused');
  }
  
  stop(): void {
    this.isPlaying = false;
    this.currentTime = 0;
    console.log('โน๏ธ Video stopped');
  }
  
  seek(time: number): void {
    this.currentTime = Math.min(time, this.duration);
    console.log(`โฉ Seeking to ${this.currentTime}s`);
  }
  
  mute(): void {
    this.muted = true;
    console.log('๐Ÿ”‡ Muted');
  }
  
  unmute(): void {
    this.muted = false;
    console.log('๐Ÿ”Š Unmuted');
  }
  
  setVolume(level: number): void {
    this.volume = Math.max(0, Math.min(100, level));
    console.log(`๐ŸŽš๏ธ Volume: ${this.volume}%`);
  }
  
  setResolution(resolution: string): void {
    this.resolution = resolution;
    console.log(`๐Ÿ“บ Resolution set to ${resolution}`);
  }
  
  enterFullscreen(): void {
    this.fullscreen = true;
    console.log('๐Ÿ“บ Entered fullscreen');
  }
  
  exitFullscreen(): void {
    this.fullscreen = false;
    console.log('๐Ÿ“ฑ Exited fullscreen');
  }
}

// ๐Ÿ“ก Streaming Player - with adaptive quality
class StreamingPlayer implements Playable, Seekable, VolumeControl, VideoControl, Streamable {
  isPlaying = false;
  currentTime = 0;
  duration = 0;
  volume = 100;
  resolution = '720p';
  bufferPercentage = 0;
  connectionQuality: 'low' | 'medium' | 'high' = 'medium';
  private muted = false;
  private fullscreen = false;
  private item: MediaItem;
  
  constructor(item: MediaItem) {
    this.item = item;
    this.duration = item.duration;
    this.startBuffering();
  }
  
  private startBuffering(): void {
    console.log('๐Ÿ“ก Buffering...');
    // Simulate buffering
    const interval = setInterval(() => {
      this.bufferPercentage += 10;
      if (this.bufferPercentage >= 100) {
        clearInterval(interval);
        console.log('โœ… Buffering complete');
      }
    }, 100);
  }
  
  play(): void {
    if (this.bufferPercentage < 30) {
      console.log('โณ Waiting for buffer...');
      return;
    }
    this.isPlaying = true;
    console.log(`๐Ÿ“ก Streaming: ${this.item.title}`);
  }
  
  pause(): void {
    this.isPlaying = false;
    console.log('โธ๏ธ Stream paused');
  }
  
  stop(): void {
    this.isPlaying = false;
    this.currentTime = 0;
    console.log('โน๏ธ Stream stopped');
  }
  
  seek(time: number): void {
    this.currentTime = Math.min(time, this.duration);
    this.bufferPercentage = 50; // Re-buffer after seek
    console.log(`โฉ Seeking to ${this.currentTime}s`);
  }
  
  mute(): void {
    this.muted = true;
    console.log('๐Ÿ”‡ Muted');
  }
  
  unmute(): void {
    this.muted = false;
    console.log('๐Ÿ”Š Unmuted');
  }
  
  setVolume(level: number): void {
    this.volume = Math.max(0, Math.min(100, level));
    console.log(`๐ŸŽš๏ธ Volume: ${this.volume}%`);
  }
  
  setResolution(resolution: string): void {
    this.resolution = resolution;
    console.log(`๐Ÿ“บ Resolution set to ${resolution}`);
  }
  
  enterFullscreen(): void {
    this.fullscreen = true;
    console.log('๐Ÿ“บ Entered fullscreen');
  }
  
  exitFullscreen(): void {
    this.fullscreen = false;
    console.log('๐Ÿ“ฑ Exited fullscreen');
  }
  
  adjustQuality(quality: 'auto' | 'low' | 'medium' | 'high'): void {
    if (quality === 'auto') {
      // Auto-adjust based on connection
      switch (this.connectionQuality) {
        case 'high':
          this.setResolution('1080p');
          break;
        case 'medium':
          this.setResolution('720p');
          break;
        case 'low':
          this.setResolution('480p');
          break;
      }
    } else {
      const resolutionMap = {
        low: '480p',
        medium: '720p',
        high: '1080p'
      };
      this.setResolution(resolutionMap[quality]);
    }
    console.log(`๐ŸŽฏ Quality adjusted to ${quality}`);
  }
}

// ๐ŸŽถ Playlist Manager - works with any playable media
class PlaylistManager implements PlaylistSupport {
  currentTrack = 0;
  playlist: MediaItem[] = [];
  repeat: 'none' | 'one' | 'all' = 'none';
  private player: Playable & Seekable;
  
  constructor(player: Playable & Seekable) {
    this.player = player;
  }
  
  addToPlaylist(item: MediaItem): void {
    this.playlist.push(item);
    console.log(`โž• Added "${item.title}" to playlist`);
  }
  
  next(): void {
    if (this.repeat === 'one') {
      this.player.seek(0);
      this.player.play();
      return;
    }
    
    this.currentTrack++;
    if (this.currentTrack >= this.playlist.length) {
      if (this.repeat === 'all') {
        this.currentTrack = 0;
      } else {
        this.currentTrack = this.playlist.length - 1;
        console.log('๐Ÿ“ End of playlist');
        return;
      }
    }
    
    console.log(`โญ๏ธ Next track: ${this.playlist[this.currentTrack].title}`);
    this.player.play();
  }
  
  previous(): void {
    this.currentTrack = Math.max(0, this.currentTrack - 1);
    console.log(`โฎ๏ธ Previous track: ${this.playlist[this.currentTrack].title}`);
    this.player.play();
  }
  
  shuffle(): void {
    for (let i = this.playlist.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [this.playlist[i], this.playlist[j]] = [this.playlist[j], this.playlist[i]];
    }
    console.log('๐Ÿ”€ Playlist shuffled');
  }
}

// ๐ŸŽฏ Usage Example
const song: MediaItem = {
  id: '1',
  title: 'Awesome Song',
  duration: 240,
  url: 'https://example.com/song.mp3'
};

const video: MediaItem = {
  id: '2',
  title: 'Cool Video',
  duration: 600,
  url: 'https://example.com/video.mp4'
};

// Create players
const audioPlayer = new AudioPlayer(song);
const videoPlayer = new VideoPlayer(video);
const streamingPlayer = new StreamingPlayer(video);

// Use playlist manager with any player
const audioPlaylist = new PlaylistManager(audioPlayer);
audioPlaylist.addToPlaylist(song);
audioPlaylist.addToPlaylist({
  id: '3',
  title: 'Another Great Song',
  duration: 180,
  url: 'https://example.com/song2.mp3'
});

// Play some music! ๐ŸŽต
audioPlayer.play();
audioPlayer.setVolume(80);
audioPlaylist.next();

Great job implementing the media player system! ๐ŸŽ‰ Notice how each player type only implements the interfaces it actually needs. This makes the code more maintainable and easier to extend!

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered interface segregation in TypeScript! Hereโ€™s what youโ€™ve learned: ๐ŸŒŸ

  1. Interface Segregation Principle - Clients shouldnโ€™t depend on interfaces they donโ€™t use ๐ŸŽฏ
  2. Focused Interfaces - Create small, cohesive interfaces for specific capabilities ๐Ÿ”
  3. Composition Over Inheritance - Combine multiple interfaces for flexibility ๐Ÿ”—
  4. Real-World Applications - Applied ISP to payment systems, smart homes, and more ๐Ÿ—๏ธ
  5. Best Practices - Design from client perspective, name by capability, group related operations ๐Ÿ“‹

Remember: Good interface design is like a well-organized toolbox โ€“ each tool has its place, and you only grab what you need! ๐Ÿงฐ

๐Ÿค Next Steps

Congratulations on completing this tutorial! ๐ŸŽ‰ Youโ€™re now ready to create beautifully segregated interfaces that make your TypeScript code more maintainable and flexible.

Hereโ€™s what you can explore next:

  • Learn about the Dependency Inversion Principle ๐Ÿ”„
  • Explore Abstract Classes vs Interfaces ๐Ÿ—๏ธ
  • Master Generic Constraints with interfaces ๐ŸŽฏ
  • Study Decorator Pattern implementations ๐ŸŽจ

Keep building amazing things with TypeScript! Your interfaces are now as focused and purposeful as a laser beam! ๐Ÿš€โœจ

Happy coding! ๐Ÿ’ป๐ŸŽˆ