Como resolver dependência circular?


33

Eu tenho 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. Tão:

  • TestExecuter depende do ReportGenerator para gerar o relatório
  • O ReportGenerator depende do TestScenario e dos parâmetros definidos no TestExecuter.
  • O TestScenario depende do 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()
    }
}

EDIT: em resposta a uma resposta, mais detalhes sobre a 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 do arquivo xml a ser gerado no caso de um cenário que contém dois testes:

<testScenario name="scenario1">
   <test name="test1">
     <result>false</result>
   </test>
   <test name="test1">
     <result>true</result>
   </test>
</testScenario >

Tente identificar seus objetos retrocedendo, perguntando o que (objeto) você precisa para o anterior funcionar - por exemplo:File(filename).write(Report); Report = XMLResult(ResultData).toString(); ResultData = TestSuite(SingleTestLogic).execute(TestDataIterator(TestDetailsList))
estremece

Respostas:


35

Tecnicamente, você pode resolver qualquer dependência cíclica usando interfaces, conforme mostrado nas outras respostas. No entanto, recomendo repensar seu design. Eu acho que não é improvável que você possa evitar completamente a necessidade de interfaces adicionais, enquanto seu design se torna ainda mais simples.

Eu acho que não é necessário que um ReportGeneratordependa de um TestScenariodiretamente. TestScenarioparece ter duas responsabilidades: é usado para execução de teste 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 dados do cenário de teste, passe os dados explicitamente usando algum objeto de valor. Isso significa, substitua

   reportGenerator.setTestScenario(ts); 

por algum código como

reportGenerator.insertDataToDisplay(ts.getReportData()); 

O método getReportDataprecisa ter um tipo de retorno como ReportData, um objeto de valor que funcione como um contêiner para os dados serem exibidos no relatório. insertDataToDisplayé um método que espera um objeto exatamente desse tipo.

Dessa maneira, ReportGeneratore TestScenarioambos dependerão ReportData, o 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 do SRP, TestScenarioseja responsável por manter os resultados de uma execução de teste, mas não por chamar o executor de teste. Considere reorganizar o código para que o cenário de teste não acesse o executor de teste, mas o executador de teste é iniciado de fora e grava os resultados novamente no TestScenarioobjeto. No exemplo que você nos mostrou, isso será possível tornando o acesso ao público LinkedList<Test>interno TestScenarioe movendo o executemétodo de TestScenariooutro lugar, talvez diretamente em uma TestExecuter, talvez em uma nova classe TestScenarioExecuter.

Dessa forma, TestExecuterdependerá TestScenarioe ReportGenerator, ReportGeneratordependerá TestScenario, também, mas TestScenariovai depender de nada mais.

E, finalmente, uma terceira abordagem: TestExecutertem muitas responsabilidades também. É responsável pela execução de testes e pelo fornecimento de um TestScenariopara a 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ê entenda a idéia geral: seu problema principal são classes com muitas responsabilidades . Resolva esse problema e você se livrará da dependência cíclica automaticamente.


Obrigado pela sua resposta, na verdade eu preciso de todas as informações no TestScenario para poder gerar meu relatório no final :(
sabrina2020 5/16

@ sabrina2020: e o que impede você de colocar todas essas informações ReportData? Você pode editar sua pergunta e explicar um pouco mais detalhadamente o que acontece dentro dele saveReport.
Doc Brown

Na verdade, meu TestScenario contém uma lista de Testes e eu quero todas as informações em um arquivo xml de relatório, para que o ReportData contenha todas as informações nesse caso. Editarei minha resposta para obter mais detalhes, obrigado!
precisa saber é o seguinte

1
+1: você me teve em interfaces.
Joel Etherton

@ sabrina2020: Adicionei duas abordagens diferentes à minha resposta, escolha a que melhor se adapte às suas necessidades.
Doc Brown

8

Usando interfaces, você pode resolver a dependência circular.

Design atual:

insira a descrição da imagem aqui

Projeto proposto:

insira a descrição da imagem aqui

No projeto proposto, as classes concretas não dependem de outras classes concretas, mas apenas de abstrações (interfaces).

Importante:

Você deve usar o padrão criacional de sua escolha (talvez uma fábrica) para evitar o aperfeiçoamento newde qualquer classe concreta dentro de qualquer outra classe ou vocação concreta getInstance(). Somente a fábrica terá dependências de classes concretas. Sua Mainclasse pode servir como fábrica se você acha que uma fábrica dedicada seria um exagero. Por exemplo, você pode injetar um ReportGeneratorno em TestExecutervez de chamar getInstance()ou new.


3

Como TestExecutorapenas usa ReportGeneratorinternamente, você deve poder definir uma interface para ela e consultar a interface em TestScenario. Então TestExecutordepende ReportGenerator, ReportGeneratordepende TestScenarioe TestScenariodepende ITestExecutor, o que não depende de nada.

Idealmente, você definiria interfaces para todas as suas classes e expressaria dependências através delas, mas essa é a menor alteração que resolverá o seu problema.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.