Как решить круговую зависимость?

33

У меня есть три класса, которые циклически зависят друг от друга:

TestExecuter выполняет запросы TestScenario и сохраняет файл отчета, используя класс ReportGenerator. Так:

  • TestExecuter зависит от ReportGenerator для создания отчета
  • ReportGenerator зависит от TestScenario и параметров, установленных из TestExecuter.
  • TestScenario зависит от TestExecuter.

Не могу понять, как удалить эти зависимости.

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

РЕДАКТИРОВАТЬ: в ответ на ответ, более подробную информацию о моем классе 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 */
    }

Пример файла xml, который будет сгенерирован в случае сценария, содержащего два теста:

<testScenario name="scenario1">
   <test name="test1">
     <result>false</result>
   </test>
   <test name="test1">
     <result>true</result>
   </test>
</testScenario >
sabrina2020
источник
Попробуйте идентифицировать ваши объекты в обратном направлении, спрашивая, какой (объект) вам нужен для того, чтобы предыдущий работал - например:File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))
дрожь

Ответы:

35

Технически, вы можете разрешить любую циклическую зависимость, используя интерфейсы, как показано в других ответах. Тем не менее, я рекомендую переосмыслить ваш дизайн. Я думаю, что не исключено, что вы сможете полностью избежать необходимости в дополнительных интерфейсах, в то время как ваш дизайн станет еще проще.

Я думаю, что нет необходимости ReportGeneratorзависеть от TestScenarioнапрямую. TestScenarioПохоже, у него есть две обязанности: он используется для выполнения теста и работает также как контейнер для результатов. Это нарушение ПСП. Интересно, что устраняя это нарушение, вы избавитесь и от циклической зависимости.

Поэтому вместо того, чтобы позволить генератору отчетов получать данные из тестового сценария, передайте данные явно, используя некоторый объект значения. Это означает, заменить

   reportGenerator.setTestScenario(ts); 

по какому-то коду

reportGenerator.insertDataToDisplay(ts.getReportData()); 

Метод getReportDataдолжен иметь тип возвращаемого значения, например ReportData, объект значения, который работает в качестве контейнера для данных, отображаемых в отчете. insertDataToDisplayэто метод, который ожидает объект именно этого типа.

Таким образом, ReportGeneratorи TestScenarioоба будут зависеть от того ReportData, что больше ни от чего не зависит, и первые два класса больше не зависят друг от друга.

В качестве второго подхода: устранить нарушение SRP, пусть TestScenarioбудет ответственным за хранение результатов выполнения теста, но не за вызов исполнителя теста. Подумайте о реорганизации кода, чтобы не тестовый сценарий обращался к исполнителю теста, а выполнял тест снаружи и записывал результаты обратно в TestScenarioобъект. В примере, который вы показали нам, это станет возможным, сделав доступ LinkedList<Test>внутрь TestScenariopublic и переместив executeметод откуда- TestScenarioто еще, может быть, непосредственно в TestExecuter, может быть, в новый класс TestScenarioExecuter.

Таким образом, TestExecuterбудет зависеть от TestScenarioи ReportGenerator, ReportGeneratorбудет зависеть от того TestScenario, тоже, но TestScenarioбудет зависеть от ничего.

И, наконец, третий подход: TestExecuterслишком много обязанностей тоже. Он отвечает за выполнение тестов, а также за предоставление TestScenarioa ReportGenerator. Поместите эти две обязанности в два отдельных класса, и ваша циклическая зависимость снова исчезнет.

Может быть больше вариантов для решения вашей проблемы, но я надеюсь, что вы поймете общую идею: вашей основной проблемой являются классы со слишком большим количеством обязанностей . Решите эту проблему, и вы автоматически избавитесь от циклической зависимости.

Док Браун
источник
Спасибо за ваш ответ, на самом деле мне нужна вся информация в TestScenario, чтобы иметь возможность сгенерировать свой отчет в конце :(
sabrina2020
@ sabrina2020: а что мешает вам поместить всю эту информацию ReportData? Вы можете отредактировать свой вопрос и объяснить немного более подробно, что происходит внутри saveReport.
Док Браун
На самом деле мой TestScenario содержит список Test, и я хочу, чтобы вся информация содержалась в XML-файле отчета, поэтому в этом случае в ReportData будет все это, и я отредактирую свой ответ для более подробной информации, спасибо!
sabrina2020
1
+1: Вы были со мной в interfaces.
Джоэл Этертон
@ sabrina2020: Я добавил два разных подхода к своему ответу, выберите тот, который больше всего соответствует вашим потребностям.
Док Браун
8

Используя интерфейсы, вы можете решить круговую зависимость.

Текущий дизайн:

введите описание изображения здесь

Предлагаемый дизайн:

введите описание изображения здесь

В предлагаемом проекте конкретные классы не зависят от других конкретных классов, а только от абстракций (интерфейсов).

Важный:

Вы должны использовать шаблон творчества по вашему выбору (возможно, фабрику), чтобы избежать newвыполнения каких-либо конкретных классов внутри любого другого конкретного класса или вызова getInstance(). Только фабрика будет зависеть от конкретных классов. Ваш Mainкласс может служить фабрикой, если вы думаете, что отдельная фабрика будет излишней. Например, вы можете ввести ReportGeneratorв TestExecuterвместо вызова getInstance()или new.

Тулаинс Кордова
источник
3

Поскольку TestExecutorиспользуется только ReportGeneratorвнутри, вы должны иметь возможность определить интерфейс для него и обратиться к интерфейсу в TestScenario. Тогда TestExecutorзависит от ReportGenerator, ReportGeneratorзависит TestScenarioи TestScenarioзависит от того ITestExecutor, что ни от чего не зависит.

В идеале вы должны определять интерфейсы для всех ваших классов и выражать через них зависимости, но это наименьшее изменение, которое решит вашу проблему.

TMN
источник