¿Cómo resolver la dependencia circular?

33

Tengo tres clases que son circulares dependientes entre sí:

TestExecuter ejecuta solicitudes de TestScenario y guarda un archivo de informe usando la clase ReportGenerator. Asi que:

  • TestExecuter depende de ReportGenerator para generar el informe
  • ReportGenerator depende de TestScenario y de los parámetros establecidos desde TestExecuter.
  • TestScenario depende de TestExecuter.

No puedo entender cómo eliminar estas dependencias.

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

EDITAR: en respuesta a una respuesta, más detalles sobre mi clase 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 */
    }

Un ejemplo del archivo xml que se generará en caso de un escenario que contenga dos pruebas:

<testScenario name="scenario1">
   <test name="test1">
     <result>false</result>
   </test>
   <test name="test1">
     <result>true</result>
   </test>
</testScenario >
sabrina2020
fuente
Intente identificar sus objetos yendo hacia atrás preguntando qué (objeto) necesita para que funcione el anterior, por ejemplo:File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))
estremecimiento el

Respuestas:

35

Técnicamente, puede resolver cualquier dependencia cíclica mediante el uso de interfaces, como se muestra en las otras respuestas. Sin embargo, recomiendo repensar su diseño. Creo que no es improbable que pueda evitar la necesidad de interfaces adicionales por completo, mientras que su diseño se vuelve aún más simple.

Supongo que no es necesario que un ReportGeneratordependa TestScenariodirectamente de un . TestScenarioparece tener dos responsabilidades: se usa para la ejecución de la prueba y también funciona como un contenedor para los resultados. Esto es una violación del SRP. Curiosamente, al resolver esa violación, también se librará de la dependencia cíclica.

Entonces, en lugar de permitir que el generador de informes tome datos del escenario de prueba, pase los datos explícitamente utilizando algún objeto de valor. Eso significa, reemplazar

   reportGenerator.setTestScenario(ts); 

por algún código como

reportGenerator.insertDataToDisplay(ts.getReportData()); 

El método getReportDatadebe tener un tipo de retorno como ReportData, un objeto de valor que funciona como un contenedor para los datos que se mostrarán en el informe. insertDataToDisplayes un método que espera un objeto exactamente de ese tipo.

De esta manera, ReportGeneratory TestScenarioambos dependerán ReportData, lo que no depende de nada más, y las dos primeras clases ya no dependen entre sí.

Como segundo enfoque: para resolver la violación de SRP, seamos TestScenarioresponsables de mantener los resultados de la ejecución de una prueba, pero no de llamar al ejecutor de la prueba. Considere reorganizar el código para que el escenario de prueba no acceda al ejecutante de prueba, pero el ejecutante de prueba se inicia desde el exterior y escribe los resultados nuevamente en el TestScenarioobjeto. En el ejemplo que nos mostró, eso será posible haciendo que el acceso al LinkedList<Test>interior del TestScenariopúblico, y moviendo el executemétodo de TestScenariootro lugar, tal vez directamente a una TestExecuter, tal vez a una nueva clase TestScenarioExecuter.

De esa manera, TestExecuterdependerá TestScenarioy ReportGenerator, ReportGeneratordependerá TestScenario, también, pero TestScenariodependerá de nada más.

Y finalmente, un tercer enfoque: TestExecutertambién tiene demasiadas responsabilidades. Es responsable de ejecutar pruebas y de proporcionar a TestScenarioa a ReportGenerator. Ponga estas dos responsabilidades en dos clases separadas, y su dependencia cíclica se desvanecerá nuevamente.

Puede haber más variantes para abordar su problema, pero espero que comprenda la idea general: su problema principal son las clases con demasiadas responsabilidades . Resuelva ese problema y se librará de la dependencia cíclica automáticamente.

Doc Brown
fuente
Gracias por su respuesta, en realidad necesito toda la información en TestScenario para poder generar mi informe al final :(
sabrina2020
@ sabrina2020: ¿y qué te impide poner toda esa información ReportData? Puede considerar editar su pregunta y explicar un poco más detallado lo que sucede dentro de saveReport.
Doc Brown
En realidad, mi TestScenario contiene una lista de Test y quiero toda la información en un archivo xml de informe, por lo que ReportData lo tendrá todo en este caso, editaré mi respuesta para obtener más detalles, ¡gracias!
sabrina2020
1
+1: Me tenías en interfaces.
Joel Etherton
@ sabrina2020: agregué dos enfoques diferentes a mi respuesta, elija el que mejor se adapte a sus necesidades.
Doc Brown
8

Mediante el uso de interfaces, puede resolver la dependencia circular.

Diseño actual:

ingrese la descripción de la imagen aquí

Diseño propuesto:

ingrese la descripción de la imagen aquí

En el diseño propuesto, las clases concretas no dependen de otras clases concretas sino solo de abstracciones (interfaces).

Importante:

Debe usar el patrón de creación que elija (tal vez una fábrica) para evitar la ejecución newde cualquier clase concreta dentro de cualquier otra clase concreta o llamada getInstance(). Solo la fábrica tendrá dependencias de clases concretas. Su Mainclase podría servir como fábrica si cree que una fábrica dedicada sería exagerada. Por ejemplo, puede inyectar un ReportGeneratoren TestExecuterlugar de llamar a getInstance()o new.

Tulains Córdova
fuente
3

Como TestExecutorsolo se usa ReportGeneratorinternamente, debe poder definir una interfaz para ella y consultar la interfaz en TestScenario. Luego TestExecutordepende ReportGenerator, ReportGeneratordepende TestScenarioy TestScenariodepende de ITestExecutor, lo que no depende de nada.

Lo ideal sería definir interfaces para todas sus clases y expresar dependencias a través de ellas, pero este es el cambio más pequeño que resolverá su problema.

TMN
fuente