Як вирішити кругову залежність?


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

EDIT: у відповідь на відповідь, більш детально про мій клас 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 >

Спробуйте визначити ваші об’єкти, що йдуть назад, запитаючи, що (об’єкт) вам потрібно для роботи попереднього - наприклад:File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))
здригнуться

Відповіді:


35

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

Я думаю, не потрібно, щоб a ReportGeneratorзалежав TestScenarioбезпосередньо.TestScenarioСхоже, має два функції відповідальності: він використовується для тестового виконання, він також працює як контейнер для результатів. Це порушення СРП. Цікаво, що вирішивши це порушення, ви позбудетесь і від циклічної залежності.

Тому замість того, щоб генератор звітів захоплював дані з тестового сценарію, передайте дані явно, використовуючи якийсь об'єкт значення. Це означає, замінити

   reportGenerator.setTestScenario(ts); 

за деяким кодом, як

reportGenerator.insertDataToDisplay(ts.getReportData()); 

Метод getReportDataповинен мати тип повернення типу ReportData, об'єкт значення, який працює як контейнер для даних, що відображаються у звіті. insertDataToDisplayце метод, який очікує об'єкта саме такого типу.

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

В якості другого підходу: для усунення порушення SRP TestScenarioнесе відповідальність за збереження результатів тестового виконання, але не за виклик виконавця тесту. Подумайте про реорганізацію коду, щоб тестовий сценарій не звертався до тестового виконавця, але тестовий виконавець запускається ззовні і записує результати назад в TestScenarioоб’єкт. У прикладі ви показали нам, що це стане можливим завдяки доступу до LinkedList<Test>всередині TestScenarioпубліки та переміщенню executeметоду з TestScenarioкудись іншого, можливо, безпосередньо в a TestExecuter, можливо, у новий клас TestScenarioExecuter.

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

І нарешті, третій підхід: TestExecuterтеж занадто багато обов'язків. Він несе відповідальність за виконання тестів, а також за надання TestScenarioа ReportGenerator. Покладіть ці два обов'язки на два окремі класи, і ваша циклічна залежність знову зникне.

Можливо, буде більше варіантів підходу до вашої проблеми, але я сподіваюся, що ви отримаєте загальне уявлення: ваша основна проблема - це класи з занадто великою кількістю обов'язків . Вирішіть цю проблему, і ви позбудетесь циклічної залежності автоматично.


Дякую за вашу відповідь, насправді мені потрібна вся інформація в TestScenario, щоб можна було в кінцевому рахунку генерувати звіт :(
sabrina2020

@ sabrina2020: а що заважає вам вносити всю цю інформацію ReportData? Ви можете відредагувати своє запитання та пояснити трохи детальніше, що відбувається всередині saveReport.
Док Браун

Насправді мій TestScenario містить список тесту, і я хочу, щоб вся інформація була у файлі 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, що ні від чого не залежить.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.