SOLID
Dependency Inversion Principle
Dépendre d'abstractions, pas d'implémentations concrètes
Principe
Deux règles :
- Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d'abstractions.
- Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions.
En pratique : un composant ne doit pas instancier directement ses dépendances. Il les reçoit via ses props ou via un mécanisme d'injection.
Violation classique
// Couplage fort : le composant connaît l'implémentation concrète
function UserList() {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
// Dépendance directe sur fetch et sur l'URL
fetch('https://api.example.com/users')
.then(res => res.json())
.then(setUsers);
}, []);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}Ce composant est impossible à tester sans mocker fetch globalement. Il est couplé à l'URL de l'API, au format de réponse, à la stratégie de fetching.
Application du DIP
// Abstraction : ce que le composant attend
interface UserRepository {
getAll(): Promise<User[]>;
}
// Implémentation concrète (bas niveau)
class ApiUserRepository implements UserRepository {
async getAll(): Promise<User[]> {
const res = await fetch('https://api.example.com/users');
return res.json();
}
}
// Module haut niveau : dépend de l'abstraction, pas de l'implémentation
function UserList({ repository }: { repository: UserRepository }) {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
repository.getAll().then(setUsers);
}, [repository]);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
// En prod
<UserList repository={new ApiUserRepository()} />
// En test
const mockRepo: UserRepository = {
getAll: async () => [{ id: '1', name: 'Alice' }],
};
<UserList repository={mockRepo} />Injection via le contexte React
Pour éviter le prop drilling, on utilise le contexte comme mécanisme d'injection :
const UserRepositoryContext = createContext<UserRepository | null>(null);
function useUserRepository() {
const repo = useContext(UserRepositoryContext);
if (!repo) throw new Error('UserRepository not provided');
return repo;
}
// Provider en haut de l'arbre
function App() {
return (
<UserRepositoryContext.Provider value={new ApiUserRepository()}>
<UserList />
</UserRepositoryContext.Provider>
);
}
// UserList consomme le contexte sans connaître l'implémentation
function UserList() {
const repository = useUserRepository();
// ...
}Lien avec les autres principes
DIP est souvent rendu possible par ISP (interfaces minimales) et par OCP (on peut changer l'implémentation sans modifier le consommateur). Les 5 principes SOLID se renforcent mutuellement.