¿Cómo resolver la dependencia circular?
On enero 1, 2021 by adminTengo tres clases que son circulares dependientes entre sí:
TestExecuter ejecuta solicitudes de TestScenario y guarda un archivo de informe usando la clase ReportGenerator . Entonces:
- 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 averiguar 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() } }
EDIT: 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 de 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 >
Comentarios
Respuesta
Técnicamente, puede resolver cualquier dependencia cíclica utilizando interfaces, como se muestra en las otras respuestas. Sin embargo, recomiendo repensar su diseño. Creo que no es improbable que puedas evitar la necesidad de interfaces adicionales por completo, mientras que tu diseño se vuelve aún más simple.
Supongo que no es necesario que un ReportGenerator
dependa de un TestScenario
directamente. TestScenario
parece tener dos responsabilidades: se utiliza para la ejecución de pruebas y también funciona como contenedor de los resultados. Esta es una violación del SRP. Curiosamente, al resolver esa infracción, también se eliminará la dependencia cíclica.
Entonces, en lugar de permitir que el generador de informes obtenga datos del escenario de prueba, pase los datos explícitamente utilizando algún objeto de valor. Eso significa, reemplace
reportGenerator.setTestScenario(ts);
por algún código como
reportGenerator.insertDataToDisplay(ts.getReportData());
El método getReportData
debe 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. insertDataToDisplay
es un método que espera un objeto de ese tipo exactamente.
De esta manera, ReportGenerator
y TestScenario
ambos dependerán de ReportData
, que no depende de nada más, y las dos primeras clases ya no dependen una de la otra.
Como segundo enfoque: para resolver la violación de SRP, deje que TestScenario
sea responsable de retener los resultados de una ejecución de prueba, pero no de llamar al ejecutador de prueba. Considere reorganizar el código para que no el escenario de prueba acceda al ejecutante de prueba, sino que el ejecutante de prueba se inicie desde fuera y escriba los resultados nuevamente en el objeto TestScenario
. En el ejemplo que nos mostró, eso será posible haciendo que el acceso a LinkedList<Test>
dentro de TestScenario
sea público y moviendo el execute
método desde TestScenario
a otro lugar, tal vez directamente en un TestExecuter
, tal vez en una nueva clase TestScenarioExecuter
.
De esa manera, TestExecuter
dependerá de TestScenario
y ReportGenerator
, ReportGenerator
también dependerá de TestScenario
, pero TestScenario
no dependerá de nada más.
Y finalmente, un tercer enfoque: TestExecuter
también tiene demasiadas responsabilidades. Es responsable de ejecutar las pruebas y de proporcionar un TestScenario
a un ReportGenerator
. Coloque estas dos responsabilidades en dos clases separadas y su dependencia cíclica desaparecerá nuevamente.
Puede haber más variantes para abordar su problema, pero espero que tenga la idea general: su problema principal son las clases con demasiadas responsabilidades . Resuelve ese problema y te librarás de la dependencia cíclica automáticamente.
Comentarios
- Gracias por tu respuesta, en realidad necesito toda la información en TestScenario para poder generar mi informe al final 🙁
- @ sabrina2020: ¿y qué le impide poner toda esa información en
ReportData
?Puede considerar editar su pregunta y explicar un poco más detalladamente lo que sucede dentro desaveReport
. - En realidad, mi TestScenario contiene una lista de Test y quiero todos información en un archivo xml de informe para que ReportData lo tenga todo en este caso, editaré mi respuesta para obtener más detalles.
.
Respuesta
Mediante el uso de interfaces, puede resolver la dependencia circular.
Diseño actual:
Diseño propuesto:
En el concreto de diseño propuesto las clases no dependen de otras clases concretas, sino solo de abstracciones (interfaces).
Importante:
Tienes que usar el patrón de creación de tu elección (tal vez una fábrica) para evitar realizar new
de cualquier clase concreta dentro de cualquier otra clase concreta o llamando a getInstance()
. Solo la fábrica tendrá dependencias de clases concretas. Su clase Main
podría servir como fábrica si cree que una fábrica dedicada sería excesiva. Por ejemplo, puede inyectar un ReportGenerator
en TestExecuter
en lugar de llamar a getInstance()
o new
.
Responder
Desde TestExecutor
solo usa ReportGenerator
internamente, debería poder definir una interfaz para él y consultar la interfaz en TestScenario
. Entonces TestExecutor
depende de ReportGenerator
, ReportGenerator
depende de TestScenario
, y TestScenario
depende de ITestExecutor
, que no depende de nada.
Idealmente «Estaría definiendo interfaces para todas sus clases y expresando dependencias a través de ellas, pero este es el cambio más pequeño que resolverá su problema.
File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))