Wie löse ich die zirkuläre Abhängigkeit?

33

Ich habe drei Klassen, die kreisförmig voneinander abhängig sind:

TestExecuter führt Anforderungen von TestScenario aus und speichert eine Berichtsdatei mit der ReportGenerator-Klasse. So:

  • TestExecuter ist von ReportGenerator abhängig, um den Bericht zu generieren
  • ReportGenerator hängt von TestScenario und den in TestExecuter festgelegten Parametern ab.
  • TestScenario ist abhängig von TestExecuter.

Kann nicht herausfinden, wie man diese Abhängigkeiten entfernt.

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()
    }
}

BEARBEITEN: als Antwort auf eine Antwort weitere Details zu meiner TestScenario-Klasse:

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 */
    }

Ein Beispiel für die XML-Datei, die bei einem Szenario mit zwei Tests generiert werden soll:

<testScenario name="scenario1">
   <test name="test1">
     <result>false</result>
   </test>
   <test name="test1">
     <result>true</result>
   </test>
</testScenario >
sabrina2020
quelle
Versuchen Sie, Ihre Objekte in umgekehrter Reihenfolge zu identifizieren und fragen Sie, was (Objekt) Sie benötigen, damit das vorherige Objekt funktioniert. Beispiel:File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))
Schaudern am

Antworten:

35

Technisch gesehen können Sie zyklische Abhängigkeiten mithilfe von Schnittstellen auflösen, wie in den anderen Antworten gezeigt. Ich empfehle jedoch, Ihr Design zu überdenken. Ich denke , es ist nicht unwahrscheinlich ist , können Sie vermeiden die Notwendigkeit für zusätzliche Schnittstellen vollständig, während Ihr Design wird noch einfacher.

Ich denke, es ist nicht notwendig, dass man sich direkt ReportGeneratorauf a TestScenarioverlässt. TestScenarioscheint zwei Verantwortlichkeiten zu haben: Es wird für die Testausführung verwendet und fungiert auch als Container für die Ergebnisse. Dies ist eine Verletzung des SRP. Interessanterweise werden Sie durch Beheben dieser Verletzung auch die zyklische Abhängigkeit beseitigen.

Anstatt den Berichtsgenerator Daten aus dem Testszenario abrufen zu lassen, übergeben Sie die Daten explizit mithilfe eines Wertobjekts. Das heißt, ersetzen

   reportGenerator.setTestScenario(ts); 

von einem Code wie

reportGenerator.insertDataToDisplay(ts.getReportData()); 

Die Methode getReportDatamuss einen Rückgabetyp wie ReportDataein Wertobjekt haben, das als Container für die im Bericht anzuzeigenden Daten fungiert. insertDataToDisplayist eine Methode, die ein Objekt genau dieses Typs erwartet.

Auf diese Weise hängen ReportGeneratorund TestScenariowerden beide davon ab ReportData, was von nichts anderem abhängt, und die ersten beiden Klassen hängen nicht mehr voneinander ab.

Als zweiten Ansatz: Um die SRP-Verletzung zu beheben, TestScenariomüssen Sie dafür verantwortlich sein, die Ergebnisse einer Testausführung zu speichern, aber nicht den Testausführer aufzurufen. Überlegen Sie, den Code so zu reorganisieren, dass nicht das Testszenario auf den Test-Executer zugreift, sondern der Test-Executer von außen gestartet wird und die Ergebnisse zurück in das TestScenarioObjekt schreibt . In dem Beispiel, das Sie uns gezeigt haben, wird dies möglich sein, indem Sie den Zugriff auf das LinkedList<Test>Innere der TestScenarioÖffentlichkeit ermöglichen und die executeMethode von einem Ort TestScenarioan einen anderen verschieben, vielleicht direkt in eine TestExecuter, vielleicht in eine neue Klasse TestScenarioExecuter.

Auf diese Weise TestExecuterwird davon abhängen , TestScenariound ReportGenerator, ReportGeneratorhängt davon ab , TestScenarioauch, aber TestScenarioauf nichts anderes ab.

Und zum Schluss noch ein dritter Ansatz: TestExecuterHat auch zu viele Verantwortlichkeiten. Es ist verantwortlich für die Durchführung von Tests sowie für die Bereitstellung von a TestScenariozu a ReportGenerator. Teilen Sie diese beiden Verantwortlichkeiten in zwei separate Klassen ein, und Ihre zyklische Abhängigkeit wird wieder verschwinden.

Möglicherweise gibt es weitere Varianten, um Ihr Problem anzugehen, aber ich hoffe, Sie haben eine allgemeine Vorstellung davon: Ihr Kernproblem sind Klassen mit zu vielen Verantwortlichkeiten . Lösen Sie dieses Problem und Sie werden die zyklische Abhängigkeit automatisch los.

Doc Brown
quelle
Danke für deine Antwort, eigentlich brauche ich alle Informationen in TestScenario, um am Ende meinen Bericht erstellen zu können :(
sabrina2020
@ sabrina2020: und was hindert dich daran, all diese Informationen in das Spiel zu stecken ReportData? Sie können in Betracht ziehen, Ihre Frage zu bearbeiten und etwas detaillierter zu erklären, was in der Frage passiert saveReport.
Doc Brown
Eigentlich enthält mein TestScenario eine Liste mit Tests und ich möchte alle Informationen in einer Report-XML-Datei haben, damit die ReportData in diesem Fall alle Informationen enthält. Ich bearbeite meine Antwort für weitere Details, danke!
Sabrina2020
1
+1: Du hattest mich bei interfaces.
Joel Etherton
@ sabrina2020: Ich habe meiner Antwort zwei verschiedene Ansätze hinzugefügt, wählen Sie den, der Ihren Bedürfnissen am besten entspricht.
Doc Brown
8

Durch die Verwendung von Schnittstellen können Sie die zirkuläre Abhängigkeit lösen.

Aktuelles Design:

Bildbeschreibung hier eingeben

Vorgeschlagenes Design:

Bildbeschreibung hier eingeben

In dem vorgeschlagenen Entwurf hängen konkrete Klassen nicht von anderen konkreten Klassen ab, sondern nur von Abstraktionen (Schnittstellen).

Wichtig:

Sie müssen das Schöpfungsmuster Ihrer Wahl (möglicherweise eine Fabrik) verwenden, um zu vermeiden, newdass konkrete Klassen innerhalb einer anderen konkreten Klasse oder eines Aufrufs ausgeführt werden getInstance(). Nur die Fabrik wird Abhängigkeiten von konkreten Klassen haben. Ihre MainKlasse könnte als Fabrik dienen, wenn Sie glauben, eine dedizierte Fabrik wäre übertrieben. Zum Beispiel können Sie ein ReportGeneratorin einfügen, TestExecuteranstatt getInstance()oder aufzurufen new.

Tulains Córdova
quelle
3

Da TestExecutornur ReportGeneratorintern verwendet, sollten Sie in der Lage sein, eine Schnittstelle dafür zu definieren und auf die Schnittstelle in zu verweisen TestScenario. Dann TestExecutorkommt es darauf an ReportGenerator, ReportGeneratorkommt darauf an TestScenariound TestScenariokommt darauf an ITestExecutor, was von nichts abhängt.

Idealerweise definieren Sie Schnittstellen für alle Ihre Klassen und drücken Abhängigkeiten durch diese aus. Dies ist jedoch die kleinste Änderung, die Ihr Problem löst.

TMN
quelle