Design Pattern
SOLID

Interface Segregation Principle

Des interfaces spécialisées plutôt qu'une interface générale

Principe

Les clients ne doivent pas être forcés de dépendre d'interfaces qu'ils n'utilisent pas. Plutôt qu'une grosse interface générale, plusieurs interfaces spécialisées.

Violation classique

interface UserService {
  getUser(id: string): Promise<User>;
  createUser(data: CreateUserDto): Promise<User>;
  updateUser(id: string, data: UpdateUserDto): Promise<User>;
  deleteUser(id: string): Promise<void>;
  getUserPermissions(id: string): Promise<string[]>;
  updateUserPermissions(id: string, permissions: string[]): Promise<void>;
  sendPasswordResetEmail(id: string): Promise<void>;
  exportUserData(id: string): Promise<Blob>;
}

Un composant qui affiche un profil utilisateur dépend de UserService et se retrouve couplé à deleteUser, sendPasswordResetEmail, exportUserData: des opérations qu'il n'utilise jamais.

Application de l'ISP

interface UserReader {
  getUser(id: string): Promise<User>;
}

interface UserWriter {
  createUser(data: CreateUserDto): Promise<User>;
  updateUser(id: string, data: UpdateUserDto): Promise<User>;
  deleteUser(id: string): Promise<void>;
}

interface UserPermissionsManager {
  getUserPermissions(id: string): Promise<string[]>;
  updateUserPermissions(id: string, permissions: string[]): Promise<void>;
}

interface UserNotifier {
  sendPasswordResetEmail(id: string): Promise<void>;
}

Chaque composant/service déclare uniquement la dépendance dont il a besoin :

// Ce composant ne dépend que de ce qu'il utilise
function UserProfile({ userId, userService }: {
  userId: string;
  userService: UserReader;
}) {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    userService.getUser(userId).then(setUser);
  }, [userId]);

  return <div>{user?.name}</div>;
}

Composition d'interfaces en TypeScript

TypeScript permet de composer des interfaces. L'implémentation concrète peut implémenter plusieurs interfaces à la fois :

class ApiUserService implements UserReader, UserWriter, UserNotifier {
  async getUser(id: string) { /* ... */ }
  async createUser(data: CreateUserDto) { /* ... */ }
  async updateUser(id: string, data: UpdateUserDto) { /* ... */ }
  async deleteUser(id: string) { /* ... */ }
  async sendPasswordResetEmail(id: string) { /* ... */ }
}

// À l'usage, on passe l'interface minimale requise
const profile = <UserProfile userId="123" userService={new ApiUserService()} />;

Lien avec les tests

ISP facilite les tests. Mocker UserReader est trivial. Mocker UserService dans sa version monolithique implique de définir des méthodes vides pour tout ce qu'on n'utilise pas.

// Mock simple possible grâce à l'interface minimale
const mockUserReader: UserReader = {
  getUser: async (id) => ({ id, name: 'Alice', email: 'alice@example.com' }),
};

render(<UserProfile userId="123" userService={mockUserReader} />);