web-dev-qa-db-fra.com

Comment résoudre la dépendance circulaire?

J'ai trois classes circulaires qui dépendent les unes des autres:

TestExecuter exécute les requêtes de TestScenario et enregistre un fichier de rapport à l'aide de la classe ReportGenerator. Donc:

  • TestExecuter dépend de ReportGenerator pour générer le rapport
  • ReportGenerator dépend de TestScenario et des paramètres définis à partir de TestExecuter.
  • TestScenario dépend de TestExecuter.

Impossible de comprendre comment supprimer ces dépendances.

public class TestExecuter {

  ReportGenerator reportGenerator;  

  public void getReportGenerator() {
     reportGenerator = ReportGenerator.getInstance();
     reportGenerator.setParams(this.params);
     /* this.params several parameters from TestExecuter class example this.owner */
  }

  public void setTestScenario (TestScenario  ts) {
     reportGenerator.setTestScenario(ts); 
  }

  public void saveReport() {
     reportGenerator.saveReport();    
  }

  public void executeRequest() {
    /* do things */
  }
}
public class ReportGenerator{
    public static ReportGenerator getInstance(){}
    public void setParams(String params){}
    public void setTestScenario (TestScenario ts){}
    public void saveReport(){}
}
public class TestScenario {

    TestExecuter testExecuter;

    public TestScenario(TestExecuter te) {
        this.testExecuter=te;
    }

    public void execute() {
        testExecuter.executeRequest();
    }
}
public class Main {
    public static void main(String [] args) {
      TestExecuter te = new TestExecuter();
      TestScenario ts = new TestScenario(te);

      ts.execute();
      te.getReportGenerator();
      te.setTestScenario(ts);
      te.saveReport()
    }
}

EDIT: en réponse à une réponse, plus de détails sur ma classe TestScenario:

public class TestScenario {
    private LinkedList<Test> testList;
    TestExecuter testExecuter;

    public TestScenario(TestExecuter te) {
        this.testExecuter=te;
    }

    public void execute() {
        for (Test test: testList) {
            testExecuter.executeRequest(test); 
        }
    }
}

public class Test {
  private String testName;
  private String testResult;
}

public class ReportData {
/*shall have all information of the TestScenario including the list of Test */
    }

Un exemple du fichier xml à générer en cas de scénario contenant deux tests:

<testScenario name="scenario1">
   <test name="test1">
     <result>false</result>
   </test>
   <test name="test1">
     <result>true</result>
   </test>
</testScenario >
37
sabrina2020

Techniquement, vous pouvez résoudre toute dépendance cyclique en utilisant des interfaces, comme indiqué dans les autres réponses. Cependant, je recommande de repenser votre conception. Je pense qu'il n'est pas improbable que vous puissiez éviter le besoin d'interfaces supplémentaires complètement, tandis que votre conception devient encore plus simple.

Je suppose qu'il n'est pas nécessaire qu'un ReportGenerator dépende directement d'un TestScenario. TestScenario semble avoir deux responsabilités: il est utilisé pour l'exécution des tests, et il fonctionne également comme un conteneur pour les résultats. Il s'agit d'une violation du SRP. Fait intéressant, en résolvant cette violation, vous vous débarrasserez également de la dépendance cyclique.

Ainsi, au lieu de laisser le générateur de rapports récupérer les données du scénario de test, passez les données explicitement en utilisant un objet valeur. Cela signifie, remplacer

   reportGenerator.setTestScenario(ts); 

par un code comme

reportGenerator.insertDataToDisplay(ts.getReportData()); 

La méthode getReportData doit avoir un type de retour comme ReportData, un objet valeur qui fonctionne comme un conteneur pour les données à afficher dans le rapport. insertDataToDisplay est une méthode qui attend un objet exactement de ce type.

De cette façon, ReportGenerator et TestScenario dépendront tous deux de ReportData, qui ne dépend de rien d'autre, et les deux premières classes ne dépendent plus l'une de l'autre.

Comme deuxième approche: pour résoudre la violation SRP, laissez TestScenario être responsable de la conservation des résultats d'une exécution de test, mais pas de l'appel de l'exécuteur de test. Envisagez de réorganiser le code afin que le scénario de test n'accède pas à l'exécuteur de test, mais l'exécuteur de test est démarré de l'extérieur et réécrit les résultats dans l'objet TestScenario. Dans l'exemple que vous nous avez montré, cela sera possible en rendant l'accès à LinkedList<Test> à l'intérieur de TestScenario public, et en déplaçant la méthode execute de TestScenario ailleurs, peut-être directement dans un TestExecuter, peut-être dans une nouvelle classe TestScenarioExecuter.

De cette façon, TestExecuter dépendra de TestScenario et ReportGenerator, ReportGenerator dépendra également de TestScenario, mais TestScenario ne dépendra de rien d'autre.

Et enfin, une troisième approche: TestExecuter a trop de responsabilités aussi. Il est responsable de l'exécution des tests ainsi que de la fourniture d'un TestScenario à un ReportGenerator. Mettez ces deux responsabilités dans deux classes distinctes et votre dépendance cyclique disparaîtra à nouveau.

Il peut y avoir plus de variantes pour aborder votre problème, mais j'espère que vous aurez l'idée générale: votre problème principal est les classes avec trop de responsabilités. Résolvez ce problème et vous vous débarrasserez automatiquement de la dépendance cyclique.

36
Doc Brown

En utilisant des interfaces, vous pouvez résoudre la dépendance circulaire.

Conception actuelle:

enter image description here

Conception proposée:

enter image description here

Dans la conception proposée, les classes concrètes ne dépendent pas d'autres classes concrètes, mais uniquement d'abstractions (interfaces).

Important:

Vous devez utiliser le motif de création de votre choix (peut-être une usine) pour éviter de parfaire new de toutes les classes de béton à l'intérieur de tout autre béton ou en appelant getInstance(). Seule l'usine aura des dépendances sur des classes concrètes. Votre classe Main pourrait servir d'usine si vous pensez qu'une usine dédiée serait exagérée. Par exemple, vous pouvez injecter un ReportGenerator dans TestExecuter au lieu d'appeler getInstance() ou new.

9
Tulains Córdova

Puisque TestExecutor utilise uniquement ReportGenerator en interne, vous devriez pouvoir définir une interface pour celle-ci et vous référer à l'interface dans TestScenario. Alors TestExecutor dépend de ReportGenerator, ReportGenerator dépend de TestScenario, et TestScenario dépend de ITestExecutor, ce qui ne fonctionne pas ' t dépendre de rien.

Idéalement, vous définiriez des interfaces pour toutes vos classes et exprimeriez les dépendances à travers elles, mais c'est le plus petit changement qui résoudra votre problème.

3
TMN