Design Pattern
SOLID

Dependency Inversion Principle

Dépendre d'abstractions, pas d'implémentations concrètes

Principe

Deux règles :

  1. Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d'abstractions.
  2. 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.