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} />);