Jak rozwiązać zależność cykliczną?

33

Mam trzy klasy, które są od siebie zależne od siebie:

TestExecuter wykonuje żądania TestScenario i zapisuje plik raportu za pomocą klasy ReportGenerator. Więc:

  • TestExecuter zależy od ReportGenerator do wygenerowania raportu
  • ReportGenerator zależy od TestScenario i parametrów ustawionych z TestExecuter.
  • TestScenario zależy od TestExecuter.

Nie mogę dowiedzieć się, jak usunąć te zależności.

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

EDYCJA: w odpowiedzi na odpowiedź, więcej szczegółów na temat mojej klasy 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 */
    }

Przykład pliku xml, który ma zostać wygenerowany w przypadku scenariusza zawierającego dwa testy:

<testScenario name="scenario1">
   <test name="test1">
     <result>false</result>
   </test>
   <test name="test1">
     <result>true</result>
   </test>
</testScenario >
sabrina2020
źródło
Spróbuj zidentyfikować swoje obiekty cofając się, pytając, czego (obiektu) potrzebujesz, aby poprzedni zadziałał - na przykład:File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))
wzdrygnij

Odpowiedzi:

35

Technicznie można rozwiązać dowolną zależność cykliczną za pomocą interfejsów, jak pokazano w innych odpowiedziach. Polecam jednak przemyśleć swój projekt. Myślę, że nie jest wykluczone, że można całkowicie uniknąć potrzeby stosowania dodatkowych interfejsów, a projekt staje się jeszcze prostszy.

Myślę, że nie jest konieczne, ReportGeneratoraby polegać TestScenariobezpośrednio na. TestScenarioWydaje się, że ma dwa elementy odpowiedzialne: służy do wykonywania testów i działa również jako pojemnik na wyniki. Jest to naruszenie SRP. Co ciekawe, rozwiązując to naruszenie, pozbędziesz się również cyklicznej zależności.

Zamiast więc pozwolić generatorowi raportów pobrać dane ze scenariusza testowego, przekaż dane jawnie, używając obiektu wartości. To znaczy, zamień

   reportGenerator.setTestScenario(ts); 

przez jakiś kod jak

reportGenerator.insertDataToDisplay(ts.getReportData()); 

Metoda getReportDatamusi mieć typ zwracany, taki jak ReportDataobiekt wartości, który działa jako kontener danych wyświetlanych w raporcie. insertDataToDisplayjest metodą, która oczekuje obiektu dokładnie tego typu.

W ten sposób, ReportGeneratori TestScenarioobie będą zależeć ReportData, co nie zależy od niczego innego, a pierwsze dwie klasy już od siebie nie zależą.

Jako drugie podejście: aby rozwiązać naruszenie zasad SRP, TestScenarioponosimy odpowiedzialność za przechowywanie wyników wykonania testu, ale nie za wywoływanie testera. Rozważ reorganizację kodu, aby scenariusz testowy nie uzyskiwał dostępu do testera, ale tester jest uruchamiany z zewnątrz i zapisuje wyniki z powrotem w TestScenarioobiekcie. W przykładzie, który pokazałeś nam, będzie to możliwe poprzez udostępnienie dostępu do LinkedList<Test>wnętrza TestScenariopubliczności i przeniesienie executemetody z TestScenarioinnego miejsca, być może bezpośrednio do, a TestExecutermoże do nowej klasy TestScenarioExecuter.

W ten sposób, TestExecuterbędzie zależeć od TestScenarioa ReportGenerator, ReportGeneratorbędzie zależeć TestScenarioteż, ale TestScenariobędzie zależeć od niczego innego.

I wreszcie trzecie podejście: TestExecuterma też zbyt wiele obowiązków. Jest odpowiedzialny za wykonywanie testów, a także za dostarczenie TestScenariodo ReportGenerator. Podziel te dwie obowiązki na dwie osobne klasy, a twoja cykliczna zależność ponownie zniknie.

Może być więcej wariantów podejścia do twojego problemu, ale mam nadzieję, że masz ogólny pomysł: twoim głównym problemem są klasy ze zbyt wieloma obowiązkami . Rozwiąż ten problem, a automatycznie pozbędziesz się cyklicznej zależności.

Doktor Brown
źródło
Dziękuję za odpowiedź, tak naprawdę potrzebuję wszystkich informacji w TestScenario, aby móc wygenerować raport na końcu :(
sabrina2020
@ sabrina2020: a co przeszkadza ci w umieszczeniu wszystkich tych informacji ReportData? Możesz rozważyć edycję swojego pytania i wyjaśnić nieco bardziej szczegółowo, co dzieje się wewnątrz saveReport.
Doc Brown
Właściwie mój TestScenario zawiera listę Testów i chcę wszystkie informacje w pliku XML raportu, więc w ReportData będzie to wszystko, w tym przypadku będę edytować swoją odpowiedź, aby uzyskać więcej szczegółów, dzięki!
sabrina2020
1
+1: Miałeś mnie na interfaces.
Joel Etherton
@ sabrina2020: Dodałem dwa różne podejścia do mojej odpowiedzi, wybierz to, które najlepiej odpowiada Twoim potrzebom.
Doc Brown
8

Za pomocą interfejsów można rozwiązać zależność cykliczną.

Obecny projekt:

wprowadź opis zdjęcia tutaj

Proponowany projekt:

wprowadź opis zdjęcia tutaj

W proponowanym projekcie konkretne klasy nie zależą od innych konkretnych klas, ale tylko od abstrakcji (interfejsów).

Ważny:

Musisz użyć wybranego przez siebie wzoru kreacji (być może fabryki), aby uniknąć newwykonania jakichkolwiek konkretnych klas wewnątrz jakiejkolwiek innej konkretnej klasy lub powołania getInstance(). Tylko fabryka będzie zależna od konkretnych klas. Twoja Mainklasa może służyć jako fabryka, jeśli uważasz, że dedykowana fabryka byłaby przesada. Na przykład można wstrzyknąć ReportGeneratordo TestExecuterzamiast dzwonić getInstance()lub new.

Tulains Córdova
źródło
3

Ponieważ TestExecutorużywa tylko ReportGeneratorwewnętrznie, powinieneś być w stanie zdefiniować dla niego interfejs i zapoznać się z interfejsem w TestScenario. Następnie TestExecutorzależy od ReportGenerator, ReportGeneratorzależy TestScenarioi TestScenariozależy od tego ITestExecutor, co nie zależy od niczego.

Idealnie byłoby zdefiniować interfejsy dla wszystkich swoich klas i wyrażać za ich pośrednictwem zależności, ale jest to najmniejsza zmiana, która rozwiąże problem.

TMN
źródło