Interface Segregation Principle (ISP)
No client should be forced to depend on methods it does not use. Create small, specific interfaces instead of large, general-purpose ones.
Core Patterns
- Split fat interfaces into focused interfaces grouped by consumer need
- Read-only clients (ReportService) depend on IReadRepository, not the full IRepository
- React components receive only the props they render, not the entire app state
- Use a container component to select and pass only the required slice of state
- Combining multiple small interfaces (IReadRepository + IWriteRepository) is valid for full-access services
4. Interface Segregation Principle (ISP)
No client should be forced to depend on methods it does not use.
Create small, specific interfaces instead of large, general-purpose ones. Clients should only know about methods they use.
Backend Example
// ❌ WRONG: Fat interface forces unnecessary dependencies
interface IWorker {
work(): void;
eat(): void;
sleep(): void;
getSalary(): number;
}
class Human implements IWorker {
work() {
/* ... */
}
eat() {
/* ... */
}
sleep() {
/* ... */
}
getSalary() {
return 50000;
}
}
class Robot implements IWorker {
work() {
/* ... */
}
eat() {
/* Robots don't eat */
}
sleep() {
/* Robots don't sleep */
}
getSalary() {
/* Robots don't get paid */ return 0;
}
}
// ✅ CORRECT: Segregated interfaces
interface IWorkable {
work(): void;
}
interface IEatable {
eat(): void;
}
interface ISleepable {
sleep(): void;
}
interface ISalaried {
getSalary(): number;
}
class Human implements IWorkable, IEatable, ISleepable, ISalaried {
work() {
/* ... */
}
eat() {
/* ... */
}
sleep() {
/* ... */
}
getSalary() {
return 50000;
}
}
class Robot implements IWorkable {
work() {
/* ... */
}
}
function makeWork(worker: IWorkable) {
worker.work(); // Only depends on work(), not eat/sleep/salary
}
Real-World Example: Repository Pattern
// ❌ WRONG: Forcing read-only clients to depend on write methods
interface IRepository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
create(entity: T): Promise<T>;
update(id: string, entity: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
}
class ReportService {
constructor(private userRepo: IRepository<User>) {} // Depends on write methods it doesn't use
async generateReport() {
const users = await this.userRepo.findAll(); // Only needs read
// ...
}
}
// ✅ CORRECT: Segregated read and write interfaces
interface IReadRepository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
find(query: Query): Promise<T[]>;
}
interface IWriteRepository<T> {
create(entity: T): Promise<T>;
update(id: string, entity: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
}
interface IRepository<T> extends IReadRepository<T>, IWriteRepository<T> {}
class ReportService {
constructor(private userRepo: IReadRepository<User>) {} // Only depends on read methods
async generateReport() {
const users = await this.userRepo.findAll();
// ...
}
}
class UserService {
constructor(private userRepo: IRepository<User>) {} // Needs both read and write
async createUser(user: User) {
await this.userRepo.create(user);
}
}
Frontend Example (React)
// ❌ WRONG: Component depends on full Redux store
interface AppState {
user: UserState;
products: ProductState;
cart: CartState;
orders: OrderState;
settings: SettingsState;
}
const UserProfile = ({ state }: { state: AppState }) => {
// Component depends on entire state but only uses user
return <div>{state.user.name}</div>;
};
// ✅ CORRECT: Component depends only on what it needs
interface UserProfileProps {
userName: string;
userEmail: string;
}
const UserProfile = ({ userName, userEmail }: UserProfileProps) => {
return (
<div>
<h1>{userName}</h1>
<p>{userEmail}</p>
</div>
);
};
// Container selects only needed data
const UserProfileContainer = () => {
const userName = useSelector((state: AppState) => state.user.name);
const userEmail = useSelector((state: AppState) => state.user.email);
return <UserProfile userName={userName} userEmail={userEmail} />;
};