Como resolver a dependência circular?
On Janeiro 1, 2021 by adminTenho três classes que são circulares dependentes uma da outra:
TestExecuter executa solicitações de TestScenario e salva um arquivo de relatório usando a classe ReportGenerator . Portanto:
- TestExecuter depende de ReportGenerator para gerar o relatório
- ReportGenerator depende de TestScenario e dos parâmetros definidos de TestExecuter.
- TestScenario depende de TestExecuter.
Não consigo descobrir como remover essas dependências.
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: em resposta a uma resposta, mais detalhes sobre minha classe 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 */ }
Um exemplo de arquivo xml a ser gerado no caso de um cenário contendo dois testes:
<testScenario name="scenario1"> <test name="test1"> <result>false</result> </test> <test name="test1"> <result>true</result> </test> </testScenario >
Comentários
Resposta
Tecnicamente, você pode resolver qualquer dependência cíclica usando interfaces, conforme mostrado nas outras respostas. No entanto, recomendo repensar seu design. Acho que não é improvável que você possa evitar a necessidade de interfaces adicionais completamente, enquanto seu design se torna ainda mais simples.
Acho que não é necessário que um ReportGenerator
dependa de um TestScenario
diretamente. TestScenario
parece ter duas responsabilidades: é usado para a execução de testes e também funciona como um contêiner para os resultados. Isso é uma violação do SRP. Curiosamente, ao resolver essa violação, você também se livrará da dependência cíclica.
Portanto, em vez de permitir que o gerador de relatórios pegue os dados do cenário de teste, passe os dados explicitamente usando algum objeto de valor. Isso significa que substitua
reportGenerator.setTestScenario(ts);
por algum código como
reportGenerator.insertDataToDisplay(ts.getReportData());
O método getReportData
precisa ter um tipo de retorno como ReportData
, um objeto de valor que funciona como um contêiner para os dados a serem exibidos no relatório. insertDataToDisplay
é um método que espera um objeto exatamente desse tipo.
Dessa forma, ReportGenerator
e TestScenario
ambas dependerão de ReportData
, que não depende de mais nada, e as duas primeiras classes não dependem mais uma da outra.
Como uma segunda abordagem: para resolver a violação SRP, deixe TestScenario
ser responsável por manter os resultados da execução de um teste, mas não por chamar o executor do teste. Considere reorganizar o código para que o cenário de teste não acesse o executor de teste, mas o executor de teste seja iniciado de fora e grava os resultados de volta no objeto TestScenario
. No exemplo que você nos mostrou, isso será possível tornando o acesso a LinkedList<Test>
dentro de TestScenario
público e movendo o execute
método de TestScenario
para outro lugar, talvez diretamente em um TestExecuter
, talvez em uma nova classe TestScenarioExecuter
.
Dessa forma, TestExecuter
dependerá de TestScenario
e ReportGenerator
, ReportGenerator
dependerá de TestScenario
também, mas TestScenario
não dependerá de mais nada.
E finalmente, uma terceira abordagem: TestExecuter
também tem muitas responsabilidades. Ele é responsável por executar testes e também por fornecer um TestScenario
a um ReportGenerator
. Coloque essas duas responsabilidades em duas classes separadas, e sua dependência cíclica desaparecerá novamente.
Pode haver mais variantes para abordar seu problema, mas espero que você tenha uma ideia geral: seu problema principal são classes com responsabilidades demais . Resolva esse problema e você se livrará da dependência cíclica automaticamente.
Comentários
- Obrigado por sua resposta, na verdade eu preciso de todas as informações no TestScenario ser capaz de gerar meu relatório no final 🙁
- @ sabrina2020: e o que o impede de colocar todas essas informações em
ReportData
?Você pode considerar editar sua pergunta e explicar um pouco mais detalhadamente o que acontece dentro desaveReport
. - Na verdade meu TestScenario contém uma lista de Testes e eu quero todos informações em um arquivo xml de relatório para que ReportData tenha tudo neste caso, vou editar minha resposta para mais detalhes, obrigado!
- +1: Você me teve em
interfaces
. - @ sabrina2020: adicionei duas abordagens diferentes à minha resposta, escolha aquela que melhor se adapta às suas necessidades.
Resposta
Usando interfaces, você pode resolver a dependência circular.
Design atual:
Design proposto:
No concreto do projeto proposto classes não dependem de outras classes concretas, mas somente de abstrações (interfaces).
Importante:
Você deve usar o padrão de criação de sua escolha (talvez uma fábrica) para evitar o desempenho new
de qualquer classe concreta dentro de qualquer outra classe concreta ou chamando getInstance()
. Apenas a fábrica terá dependências de classes concretas. Sua Main
classe poderia servir como fábrica se você achar que uma fábrica dedicada seria um exagero. Por exemplo, você pode injetar um ReportGenerator
em TestExecuter
em vez de chamar getInstance()
ou new
.
Resposta
Desde TestExecutor
usa apenas ReportGenerator
internamente, você deve ser capaz de definir uma interface para ele e consultar a interface em TestScenario
. Então TestExecutor
depende de ReportGenerator
, ReportGenerator
depende de TestScenario
e TestScenario
depende de ITestExecutor
, que não depende de nada.
Idealmente, você “d definiria interfaces para todas as suas classes e expressaria dependências por meio delas, mas esta é a menor mudança que resolverá seu problema.
File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))