• 4 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 30/10/2024

« D » pour Dependency Inversion, le principe d'inversion des dépendances

Le principe d'inversion des dépendances semble toujours plus compliqué qu'il ne l'est en réalité. Voici l'idée : 

Les classes de haut niveau ne sont pas censées changer à cause des modifications réalisées dans les classes de bas niveau.

Les classes de haut niveau sont généralement celles qui pilotent notre système. Vous souhaitez qu'elles restent aussi stables que possible.

Pourquoi utiliser l'inversion des dépendances ?

Prenons l'exemple de la conduite, en particulier des voitures. Les classes de haut niveau sont les éléments avec lesquels vous êtes le plus en interaction : le volant, l'accélérateur et la pédale de frein. Elles indiquent aux classes d'implémentation de bas niveau (pneus, moteur, freins) ce qu'elles doivent faire.

Voyons ce qu'il se passe avec les classes de haut niveau si vous passez d'un moteur à essence à un moteur électrique... Rien ! Un automobiliste continue de conduire, d'accélérer et de freiner à l'aide des mêmes commandes. Si vous aviez enfreint le principe D, le passage à l'électrique (classe de bas niveau) aurait rendu obligatoire une modification de l'interface (une classe de haut niveau) pour le conducteur. Ce qui semble évident pour une voiture peut facilement tourner à la désorganisation dans du code.

Vous attribuez des responsabilités aux classes de haut niveau. Elles définissent l'interface via laquelle elles communiquent. Dans la voiture, vous faites accélérer le moteur en appuyant sur la pédale d'accélérateur. Il incombe au moteur de se conformer à cette norme.

Appliquez l'inversion des dépendances à votre code

Dans notre jeu, la vue doit être « pilotée » par le contrôleur. Le contrôleur décide donc de l'interface. Toutes les vues doivent s'y conformer. Sinon, à chaque fois que vous modifiez l'interface, le contrôleur devrait être modifié pour se conformer à la vue (le processus s'effectue de manière rétroactive).

Ajoutons une GUI simple. Nous savons quelle interface elle doit implémenter (GameViewable). L'ajout de cette nouvelle vue ne devrait pas entraîner de modifications du contrôleur. Elle devrait parfaitement s'intégrer.

Créons-la ensemble :

Voici une implémentation simple de la GUI.

package com.openclassrooms.cardgame.view;

import java.awt.Component;
import java.awt.Container;

import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import com.openclassrooms.cardgame.controller.GameController;

public class GameSwingView implements GameViewable {

	GameController controller;
	JTextArea textArea;
	JFrame frame;

	public void createAndShowGUI() {

		// create main frame
		frame = new JFrame("MVC-SOLID-Game");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(500, 500);

		// display vertically
		Container contentPane = frame.getContentPane();
		contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
		
        addControllerCommandTracker(contentPane);

		frame.setVisible(true);
	}
	
    // a simple place to display what the controller is telling us
    // very similar to our command line version    
    private void addControllerCommandTracker(Container contentPane) {
	    textArea = new JTextArea("Game Status\n", 100, 1);
	    JScrollPane scrollPane = new JScrollPane(textArea);
	    addCenteredComponent(scrollPane, contentPane);
	    textArea.setSize(500, 500);
    }

    // all controls are added so they are centered horizontally in the area
    private void addCenteredComponent(JComponent component, Container contentPane) {
        component.setAlignmentX(Component.CENTER_ALIGNMENT);
        contentPane.add(component);
    }

    
	private void appendText(String text) {
		textArea.append(text + "\n");
		textArea.setCaretPosition(textArea.getDocument().getLength());
	}
	

	@Override
	public void setController(GameController controller) {

		this.controller = controller;

	}

	@Override
	public void showPlayerName(int playerIndex, String playerName) {
		appendText("[" + playerIndex + "][" + playerName + "]");
	}

	@Override
	public void showCardForPlayer(int playerIndex, String playerName, String cardRank, String cardSuit) {
		appendText("[" + playerName + "][" + cardRank + ":" + cardSuit + "]");
	}

	@Override
	public void showWinner(String winnerName) {
		appendText("Winner!\n" + winnerName);
	}

	@Override
	public void showFaceDownCardForPlayer(int playerIndex, String name) {
		appendText("[" + name + "][][]");
	}

	@Override
	public void promptForPlayerName() {
		
		String result = (String) JOptionPane.showInputDialog(frame, "Add a player", "Player",
				JOptionPane.PLAIN_MESSAGE, null, null, "");
		
		if(result == null || result.isEmpty()) {
			controller.nextAction("+q");
		}
		
		controller.addPlayer(result);
		
		int addMore = JOptionPane.showConfirmDialog(frame, "Add more players ?", "More players", JOptionPane.YES_NO_OPTION);
		
		if( addMore == JOptionPane.NO_OPTION) {
			controller.startGame();
		}
	}

	@Override
	public void promptForFlip() {
		controller.flipCards();
	}

	@Override
	public void promptForNewGame() {
		int newGame = JOptionPane.showConfirmDialog(frame, "Play again ?", "Play again", JOptionPane.YES_NO_OPTION);
		controller.nextAction(newGame == JOptionPane.NO_OPTION ? "+q" : "");
	}
	

}

La façon la plus simple d'enfreindre le principe D est d'en savoir trop sur les implémentations. Si vous connaissez le fonctionnement détaillé d'une implémentation, il devient tentant d'aller y écrire du code.

Bon ! C'est une bonne chose que vous ayez appliqué les principes SOLID. Mais si vous ne l'aviez pas fait ? Il existe un endroit, entre le contrôleur et la vue, où peuvent être intégrés des couplages inutiles. Nous avons créé une interface pour les appels du contrôleur, et la vue qui l'implémente. Mais que se passe-t-il si le contrôleur modifie directement les composants graphiques de la vue ? Dans ce cas, le code GameController, ça ressemblerait à ceci :

public void addPlayerName(String playerName) {
    
   // this knows too much about the view's implementation
   view.textArea.append("[" + playerIndex + "][" + playerName +"]\n");
   
}

Imaginons que vous vouliez modifier l'interface utilisateur afin d'améliorer son apparence. Remplacez la commande JTextArea par un ensemble de JLabel. Étant donné que le contrôleur connaît l'implémentation spécifique utilisée par la vue, il doit modifier son propre code pour gérer les nouvelles commandes. En d'autres termes, l'implémentation spécifique (implémentation GUI/de bas niveau) pilote à présent l'implémentation de haut niveau (le contrôleur). Mauvaise idée !

En résumé

  • L'inversion des dépendances nous dit que les concepts de haut niveau doivent communiquer par le biais d'abstractions de haut niveau. En d'autres termes : les classes de haut niveau ne devraient pas avoir à changer à cause des modifications apportées aux classes de bas niveau.

  • Faites attention à ne pas en connaître trop sur les implémentations de bas niveau. :) 

Dans le chapitre suivant, nous examinerons plusieurs mauvaises pratiques ou pièges faciles inhérents à la programmation, et nous découvrirons comment les éviter. 

Exemple de certificat de réussite
Exemple de certificat de réussite