Comment résoudre la dépendance circulaire?

33

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

TestExecuter exécute les demandes de TestScenario et enregistre un fichier de rapport à l'aide de la classe ReportGenerator. Alors:

  • TestExecuter dépend de ReportGenerator pour générer le rapport
  • ReportGenerator dépend de TestScenario et des paramètres définis par 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 */
    }

Exemple de fichier XML à générer dans le cas d’un scénario contenant deux tests:

<testScenario name="scenario1">
   <test name="test1">
     <result>false</result>
   </test>
   <test name="test1">
     <result>true</result>
   </test>
</testScenario >
sabrina2020
la source
Essayez d'identifier vos objets en revenant en arrière en demandant de quoi (objet) avez-vous besoin pour que le précédent fonctionne - par exemple:File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))
frémissez le

Réponses:

35

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 complètement le besoin d'interfaces supplémentaires, tandis que votre conception devient encore plus simple.

Je suppose qu’il n’est pas nécessaire que cela ReportGeneratordépende TestScenariodirectement d’ un . TestScenariosemble avoir deux responsabilités: il est utilisé pour l'exécution du test et fonctionne également comme un conteneur pour les résultats. Ceci est une violation du PÉR. Fait intéressant, en résolvant cette violation, vous vous débarrasserez également de la dépendance cyclique.

Par conséquent, au lieu de laisser le générateur de rapports récupérer les données du scénario de test, transmettez-les explicitement à l'aide d'un objet de valeur. Cela signifie, remplace

   reportGenerator.setTestScenario(ts); 

par un code comme

reportGenerator.insertDataToDisplay(ts.getReportData()); 

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

De cette façon, ReportGeneratoret TestScenariodépendra , tout ReportData, qui dépend de rien d' autre, et les deux premières classes ne dépendent pas de l'autre plus.

Dans une deuxième approche: pour résoudre la violation SRP, laissez TestScenariola responsabilité de conserver les résultats d'une exécution de test, mais pas d'appeler l'exécuteur de test. Envisagez de réorganiser le code pour que le scénario de test n'accède pas à l'exécuteur de test, mais que l'exécuteur de test soit démarré de l'extérieur et enregistre les résultats dans l' TestScenarioobjet. Dans l'exemple que vous nous avez montré, cela sera possible en rendant l'accès à l' LinkedList<Test>intérieur du TestScenariopublic et en déplaçant la executeméthode d'un TestScenarioendroit à un autre, peut-être directement dans une TestExecuterclasse, peut-être dans une nouvelle classe TestScenarioExecuter.

De cette façon, TestExecuterdépendra TestScenarioet ReportGenerator, ReportGeneratordépendra TestScenarioaussi, mais TestScenariodépendra de rien d' autre.

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

Il y a peut-être plus de variantes pour aborder votre problème, mais j'espère que vous aurez une idée générale: votre problème central est constitué de classes comportant trop de responsabilités . Résolvez ce problème et vous vous débarrasserez automatiquement de la dépendance cyclique.

Doc Brown
la source
Merci pour votre réponse. En fait, j'ai besoin de toutes les informations de TestScenario pour pouvoir générer mon rapport à la fin :(
sabrina2020
@ sabrina2020: et qu'est-ce qui vous empêche de mettre toute cette information dans ReportData? Vous pouvez envisager de modifier votre question et d'expliquer un peu plus en détail ce qui se passe à l'intérieur de saveReport.
Doc Brown
En fait, mon TestScenario contient une liste de tests et je veux toutes les informations dans un fichier XML de rapport afin que le ReportData ait tout dans ce cas, je modifierai ma réponse pour plus de détails, merci!
sabrina2020
1
+1: Vous m'avez eu à interfaces.
Joel Etherton
@ sabrina2020: j'ai ajouté deux approches différentes à ma réponse, choisissez celle qui correspond le mieux à vos besoins.
Doc Brown
8

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

Conception actuelle:

entrez la description de l'image ici

Conception proposée:

entrez la description de l'image ici

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

Important:

Vous devez utiliser le modèle de création de votre choix (peut-être une usine) pour éviter de définir newdes classes concrètes dans des classes ou des appels concrets getInstance(). Seule l'usine aura des dépendances sur des classes concrètes. Votre Mainclasse pourrait servir d’usine si vous pensez qu’une usine dédiée serait exagérée. Par exemple, vous pouvez injecter un ReportGeneratoren TestExecuterau lieu d'appeler getInstance()ou new.

Tulains Córdova
la source
3

Etant donné TestExecutorque n'utilise qu'en ReportGeneratorinterne, vous devriez pouvoir définir une interface pour cette interface et vous reporter à l'interface dans TestScenario. Cela TestExecutordépend ReportGenerator, ReportGeneratordépend TestScenario, et TestScenariodépend ITestExecutor, qui ne dépend de rien.

Dans l’idéal, vous définissez des interfaces pour toutes vos classes et exprimez des dépendances à travers elles, mais c’est le plus petit changement qui résoudra votre problème.

RGT
la source